[
  {
    "path": ".gitignore",
    "content": "# C++ objects and libs\n*.slo\n*.lo\n*.o\n*.a\n*.la\n*.lai\n*.so\n*.dll\n*.dylib\n\n# Qt-es\nobject_script.*.Release\nobject_script.*.Debug\n*_plugin_import.cpp\n/.qmake.cache\n/.qmake.stash\n*.pro.user\n*.pro.user.*\n*.qbs.user\n*.qbs.user.*\n*.moc\nmoc_*.cpp\nmoc_*.h\nqrc_*.cpp\nui_*.h\n*.qmlc\n*.jsc\nMakefile*\n*build-*\n\n# Qt unit tests\ntarget_wrapper.*\n\n# QtCreator\n*.autosave\n\n# QtCreator Qml\n*.qmlproject.user\n*.qmlproject.user.*\n\n# QtCreator CMake\nCMakeLists.txt.user*\n.DS_Store\n.idea\n*~\n.*~\n*.cbp\nbuild\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"gsl\"]\n\tpath = gsl\n\turl = https://github.com/microsoft/GSL.git\n[submodule \"gtest\"]\n\tpath = gtest\n\turl = https://github.com/google/googletest.git\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## 1.1.0 - 2020-05-31\n* Export subset of font characters (only the characters that you really\n  need in the font) - helps to reduce font size when you only use a bunch\n  of characters and not the whole ASCII table\n* Exported source code is wrapped at around 80 columns\n* Added pseudocode in the generated source code, explaining how to retrieve\n  individual font glyphs\n* More user-friendly Undo/Redo functionality\n* Added checking for updates on-demand from the menu and automatically\n  at start-up (the latter can be disabled)\n\n\n## 1.0.0 - 2020-03-20\n* First public release\n* Importing system fonts\n* Editing font glyphs\n* Adding new glyphs\n* Exporting source code\n* Saving current state of the font document\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.9)\nproject(FontEdit)\n\nset(CMAKE_PROJECT_VERSION_MAJOR 1)\nset(CMAKE_PROJECT_VERSION_MINOR 1)\nset(CMAKE_PROJECT_VERSION_PATCH 0)\n\nset(APP_VERSION 1.1.0)\nset(APP_BUILD 4)\nset(APP_YEAR 2020)\n\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\nset(BUILD_TESTS ON)\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})\n\nfind_package(Qt5 COMPONENTS Widgets REQUIRED)\n\nenable_testing()\nadd_subdirectory(gtest EXCLUDE_FROM_ALL)\nadd_subdirectory(gsl EXCLUDE_FROM_ALL)\nadd_subdirectory(lib)\nadd_subdirectory(app)\nif(${BUILD_TESTS})\n    add_subdirectory(test)\nendif()\n\nif (APPLE)\n    add_custom_command(OUTPUT \"${APP_TARGET_NAME}.dmg\"\n        COMMAND macdeployqt\n        ARGS \"${APP_TARGET_NAME}.app\" \"-libpath=/lib\" \"-dmg\"\n        MAIN_DEPENDENCY ${APP_TARGET_NAME}.app\n        WORKING_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}\")\n\n    add_custom_target(dmg\n        DEPENDS \"${CMAKE_CURRENT_BINARY_DIR}/${APP_TARGET_NAME}.dmg\"\n        )\nelseif (WIN32)\n    message(STATUS \"ADDING WINDOWS INSTALLER TARGET\")\n    add_custom_command(OUTPUT \"win\"\n        COMMAND windeployqt.exe\n        ARGS \"${APP_TARGET_NAME}.exe\" \"--dir\" \"win\"\n        WORKING_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}\")\n\n    add_custom_target(installer\n        DEPENDS \"${CMAKE_CURRENT_BINARY_DIR}/win\"\n        )\nendif ()\n\n\nset(CPACK_PACKAGE_VERSION \"1.1.0\")\nset(CPACK_PACKAGE_CONTACT \"dominik@kapusta.cc\")\nset(CPACK_PROJECT_HOMEPAGE_URL \"https://kapusta.cc\")\nset(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)\nset(CPACK_DEBIAN_PACKAGE_NAME \"fontedit\")\nset(CPACK_DEBIAN_PACKAGE_RELEASE 3)\nset(CPACK_DEBIAN_PACKAGE_DESCRIPTION\n    \"Edit and convert fonts to byte arrays suitable for use in embedded systems' displays\")\nset(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)\ninclude(CPack)\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at dominik@kapusta.cc. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>."
  },
  {
    "path": "README.md",
    "content": "# FontEdit\n\nFontEdit is a desktop application that allows you to convert general-purpose \nfixed-width desktop fonts to byte array representation that's suitable for\nuse in embedded systems displays.\n\nIt's written in C++ with Qt UI and was tested on Windows, Linux and MacOS.\n\nRead more about it in [the blog post](https://kapusta.cc/2019/03/20/fontedit/).\n\n![FontEdit](https://kapusta.cc/assets/fontedit/imported_font.png)\n\n## Features\n\nWith FontEdit you can:\n\n* import fonts from the operating system - to load a custom font, you should\n  first register it in your OS,\n* edit individual font glyphs after importing - automatic import is a best-effort\n  operation and although the font should be usable right after importing, you\n  might want to tweak it so that it looks better,\n* add new glyphs to a font document - either by copying an existing glyph, starting \n  from scratch or adding a glyph from a character you input (useful for adding\n  non-ASCII characters to your font),\n* export the font as source code (in a form of byte array) suitable for Arduino,\n  C/C++ or Python,\n* save your progress to a file - the font document file is cross-platform so you can\n  e.g. import and edit it on MacOS and then move to RPi and export the code from there,\n* as of 1.1.0 you can do partial exports, i.e. export only a bunch of font characters\n  that you really need for your application (read more in \n  [this blog post](https://kapusta.cc/2020/05/31/fontedit-1-1-0)).\n\n### Font Editor\n\nYou can edit font glyphs with a minimal editor that's controlled with a mouse\nand keyboard. Click and drag the mouse to set pixels (making them black), hold\nAlt or Ctrl (⌘) to erase. Use touchpad scroll (mouse wheel) with Ctrl (⌘) to zoom\nthe editor canvas.\n\nYou can also reset the current glyph or the whole font to their initial state\n(from latest save). The editor supports Undo/Redo for most operations.\n\n### Source Code Export\n\nThe font data can be exported to:\n\n* a C file (also suitable for use with C++),\n* an Arduino-specific C file (using PROGMEM),\n* a Python list or bytes object (both compatible with Python 2.x/3.x and MicroPython).\n\nYou can switch between MSB and LSB mode, invert all the bits, and conditionally include\nline spacings in font definition (not recommended unless you have a very good reason\nfor it). The tab size can be configured.\n\n## Getting FontEdit\n\n### Packages\n\nThe [Releases GitHub page](https://github.com/ayoy/fontedit/releases) contains\npackages for:\n* Ubuntu/Debian (amd64),\n* Raspbian Buster (armhf),\n* MacOS,\n* Windows.\n\n### Building from source\n\nPrerequisites:\n\n* Qt (tested with >= 5.9)\n* cmake (3.9 or newer)\n* C++ compiler that supports C++17\n\nFollow these steps to build the app from the source code:\n\n1. Clone the Git repository:\n\n    ```\n    $ git clone https://github.com/ayoy/fontedit\n    $ cd fontedit\n    ```\n\n2. Check out Git submodules:\n\n    ```\n    $ git submodule update --init\n    ```\n\n3. Build with CMake:\n\n    ```\n    $ mkdir build\n    $ cd build\n    $ cmake -DCMAKE_BUILD_TYPE=Release ..\n    $ make\n    ```\n\n4. (Optionally) Install on Linux with: `make install` or create a dmg\n  image on MacOS with `make dmg`.\n\n## Bugs, ideas, improvements\n\nPlease report bugs and feature requests via [GitHub Issues](https://github.com/ayoy/fontedit/issues) or as a [pull request](https://github.com/ayoy/fontedit/pulls).\n\n## License\n\n© 2020 Dominik Kapusta\n\nThis app is distributed in accordance with GPL v3. See [LICENSE](https://github.com/ayoy/fontedit/blob/master/LICENSE) for details.\nThe app uses icons from [www.flaticon.com](https://www.flaticon.com) made by\n[Smashicons](https://www.flaticon.com/authors/smashicons),\n[Freepik](https://www.flaticon.com/authors/freepik) and\n[Pixel perfect](https://www.flaticon.com/authors/pixel-perfect).\n"
  },
  {
    "path": "app/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.9)\n\nif (UNIX AND NOT APPLE)\n    project(fontedit LANGUAGES CXX)\nelse()\n    project(FontEdit LANGUAGES CXX)\nendif()\n\nset(APP_TARGET_NAME ${PROJECT_NAME} PARENT_SCOPE)\n\nset(CMAKE_INCLUDE_CURRENT_DIR ON)\n\nset(CMAKE_AUTOUIC ON)\nset(CMAKE_AUTOMOC ON)\nset(CMAKE_AUTORCC ON)\n\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\nfind_package(Qt5 COMPONENTS Core Gui Widgets Network REQUIRED)\nfind_package(Qt5LinguistTools)\n\nadd_subdirectory(utf8)\nadd_subdirectory(common)\nadd_subdirectory(ui)\n\nset(SRC_FILES\n    addglyphdialog.cpp\n    addglyphdialog.h\n    addglyphdialog.ui\n    command.h\n    fontfaceviewmodel.cpp\n    fontfaceviewmodel.h\n    global.h\n    mainwindow.cpp\n    mainwindow.h\n    mainwindow.ui\n    mainwindowmodel.cpp\n    mainwindowmodel.h\n    qfontfacereader.cpp\n    qfontfacereader.h\n    semver.hpp\n    sourcecoderunnable.cpp\n    sourcecoderunnable.h\n    updatehelper.cpp\n    updatehelper.h\n    )\n\nadd_library(appbundle ${SRC_FILES})\ntarget_include_directories(appbundle PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} utf8 common ui)\ntarget_link_libraries(appbundle PUBLIC Qt5::Widgets Qt5::Core Qt5::Network common ui font2bytes GSL)\n\nif (APPLE)\n    message(STATUS \"Building MacOS X Bundle\")\n    add_executable(${PROJECT_NAME} MACOSX_BUNDLE\n        main.cpp\n        assets.qrc\n        )\n\n    set(CMAKE_OSX_DEPLOYMENT_TARGET 10.14)\n    set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${PROJECT_SOURCE_DIR}/macos/Info.plist)\n    set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE_ICON_FILE fontedit.icns)\n\n    set(RESOURCES_DIR ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}.app/Contents/Resources)\n    file(MAKE_DIRECTORY ${RESOURCES_DIR})\n    file(COPY ${PROJECT_SOURCE_DIR}/macos/fontedit.icns DESTINATION ${RESOURCES_DIR})\nelseif(WIN32)\n    set(OPENSSL_ROOT_DIR \"${Qt5_DIR}/../../../../Tools/OpenSSL/Win_x64\" CACHE STRING \"OpenSSL dir\")\n    include(FindOpenSSL)\n    add_executable(${PROJECT_NAME} WIN32\n        main.cpp\n        assets.qrc\n        win/fontedit.rc\n        )\n    target_link_libraries(${PROJECT_NAME} PRIVATE OpenSSL::SSL OpenSSL::Crypto)\nelse()\n    add_executable(${PROJECT_NAME}\n        main.cpp\n        assets.qrc\n        )\n    if (UNIX)\n        install(TARGETS ${PROJECT_NAME}\n            RUNTIME DESTINATION bin\n            LIBRARY DESTINATION lib)\n        install(FILES x11/fontedit.desktop DESTINATION share/applications)\n        install(DIRECTORY x11/icons DESTINATION share)\n\n        # uninstall target\n        if(NOT TARGET uninstall)\n            configure_file(\n                \"${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in\"\n                \"${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake\"\n                IMMEDIATE @ONLY)\n\n            add_custom_target(uninstall\n            COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)\n        endif()\n\n    endif()\nendif()\n\ntarget_include_directories(${PROJECT_NAME} PRIVATE appbundle)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE appbundle)\n\ntarget_compile_definitions(${PROJECT_NAME} PRIVATE VERSION=\"${APP_VERSION}\" BUILD=\"${APP_BUILD}\" YEAR=\"${APP_YEAR}\")\ntarget_compile_definitions(appbundle PRIVATE VERSION=\"${APP_VERSION}\" BUILD=\"${APP_BUILD}\" YEAR=\"${APP_YEAR}\")\ntarget_compile_definitions(ui PRIVATE VERSION=\"${APP_VERSION}\" BUILD=\"${APP_BUILD}\" YEAR=\"${APP_YEAR}\")\n"
  },
  {
    "path": "app/addglyphdialog.cpp",
    "content": "#include \"addglyphdialog.h\"\n#include \"./ui_addglyphdialog.h\"\n#include \"facewidget.h\"\n#include \"qfontfacereader.h\"\n\nAddGlyphDialog::AddGlyphDialog(const FontFaceViewModel& faceViewModel, QWidget *parent) :\n    QDialog(parent),\n    ui_ { new Ui::AddGlyphDialog }\n{\n    ui_->setupUi(this);\n\n    faceScene_->setBackgroundBrush(QBrush(Qt::lightGray));\n    faceWidget_ = new FaceWidget(7);\n\n    ui_->titleLabel->setText(tr(\"Add a new Glyph to %1\").arg(faceViewModel.faceInfo().fontName));\n\n    ui_->faceGraphicsView->setScene(faceScene_.get());\n    ui_->faceGraphicsView->scene()->addItem(faceWidget_);\n\n    faceWidget_->load(faceViewModel.face(), f2b::font::margins {});\n    connect(faceWidget_, &FaceWidget::currentGlyphIndexChanged, ui_->copyRadio, &QRadioButton::click);\n    connect(faceWidget_, &FaceWidget::currentGlyphIndexChanged, [&, faceViewModel](std::optional<std::size_t> index) {\n        if (index.has_value())\n            newGlyph_ = faceViewModel.face().glyph_at(index.value());\n    });\n    connect(ui_->buttonBox, &QDialogButtonBox::accepted, [&, faceViewModel] {\n        if (ui_->emptyRadio->isChecked()) {\n            newGlyph_ = f2b::font::glyph { faceViewModel.face().glyphs_size() };\n        } else if (ui_->characterRadio->isChecked()) {\n            QFontFaceReader adapter {\n                faceViewModel.font().value(),\n                ui_->characterLineEdit->text().toStdString(),\n                faceViewModel.face().glyphs_size()\n            };\n            newGlyph_ = f2b::font::face(adapter).glyph_at(0);\n        }\n        emit glyphSelected(newGlyph_);\n    });\n\n    if (faceViewModel.font().has_value()) {\n        ui_->characterRadio->setEnabled(true);\n        ui_->characterErrorLabel->setVisible(false);\n        ui_->characterLineEdit->setVisible(true);\n    } else {\n        ui_->characterRadio->setEnabled(false);\n        ui_->characterErrorLabel->setVisible(true);\n        ui_->characterLineEdit->setVisible(false);\n    }\n}\n\nAddGlyphDialog::~AddGlyphDialog()\n{\n    delete ui_;\n}\n"
  },
  {
    "path": "app/addglyphdialog.h",
    "content": "#ifndef ADDGLYPHDIALOG_H\n#define ADDGLYPHDIALOG_H\n\n#include <QDialog>\n#include <QGraphicsScene>\n#include <memory>\n#include <optional>\n#include \"fontfaceviewmodel.h\"\n#include \"f2b.h\"\n\nnamespace Ui {\nclass AddGlyphDialog;\n}\n\nclass FaceWidget;\n\nclass AddGlyphDialog : public QDialog\n{\n    Q_OBJECT\n\npublic:\n    explicit AddGlyphDialog(const FontFaceViewModel& faceViewModel, QWidget *parent = nullptr);\n    ~AddGlyphDialog();\n\nsignals:\n    void glyphSelected(const std::optional<f2b::font::glyph>& glyph);\n\nprivate:\n    Ui::AddGlyphDialog *ui_;\n    FaceWidget *faceWidget_ { nullptr };\n    std::unique_ptr<QGraphicsScene> faceScene_ { std::make_unique<QGraphicsScene>() };\n\n    std::optional<f2b::font::glyph> newGlyph_ {};\n};\n\n#endif // ADDGLYPHDIALOG_H\n"
  },
  {
    "path": "app/addglyphdialog.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>AddGlyphDialog</class>\n <widget class=\"QDialog\" name=\"AddGlyphDialog\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>624</width>\n    <height>539</height>\n   </rect>\n  </property>\n  <property name=\"maximumSize\">\n   <size>\n    <width>624</width>\n    <height>16777215</height>\n   </size>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Add New Glyph</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <property name=\"spacing\">\n    <number>8</number>\n   </property>\n   <item>\n    <widget class=\"QLabel\" name=\"titleLabel\">\n     <property name=\"text\">\n      <string>Adding Glyph for </string>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\n     <item>\n      <widget class=\"QRadioButton\" name=\"emptyRadio\">\n       <property name=\"text\">\n        <string>Add empty Glyph</string>\n       </property>\n       <property name=\"checked\">\n        <bool>true</bool>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n     <item>\n      <widget class=\"QRadioButton\" name=\"characterRadio\">\n       <property name=\"enabled\">\n        <bool>true</bool>\n       </property>\n       <property name=\"text\">\n        <string>Initialize with font character:</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QLabel\" name=\"characterErrorLabel\">\n       <property name=\"enabled\">\n        <bool>false</bool>\n       </property>\n       <property name=\"text\">\n        <string>Current document font not found in system</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QLineEdit\" name=\"characterLineEdit\">\n       <property name=\"enabled\">\n        <bool>true</bool>\n       </property>\n       <property name=\"maxLength\">\n        <number>1</number>\n       </property>\n       <property name=\"placeholderText\">\n        <string>Type character here</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <spacer name=\"horizontalSpacer\">\n       <property name=\"orientation\">\n        <enum>Qt::Horizontal</enum>\n       </property>\n       <property name=\"sizeHint\" stdset=\"0\">\n        <size>\n         <width>40</width>\n         <height>20</height>\n        </size>\n       </property>\n      </spacer>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout_3\">\n     <item>\n      <widget class=\"QRadioButton\" name=\"copyRadio\">\n       <property name=\"text\">\n        <string>Copy existing Glyph:</string>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <widget class=\"QGraphicsView\" name=\"faceGraphicsView\">\n     <property name=\"enabled\">\n      <bool>true</bool>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QDialogButtonBox\" name=\"buttonBox\">\n     <property name=\"orientation\">\n      <enum>Qt::Horizontal</enum>\n     </property>\n     <property name=\"standardButtons\">\n      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>\n     </property>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <tabstops>\n  <tabstop>emptyRadio</tabstop>\n  <tabstop>characterRadio</tabstop>\n  <tabstop>characterLineEdit</tabstop>\n  <tabstop>copyRadio</tabstop>\n  <tabstop>faceGraphicsView</tabstop>\n </tabstops>\n <resources/>\n <connections>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>rejected()</signal>\n   <receiver>AddGlyphDialog</receiver>\n   <slot>reject()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>328</x>\n     <y>534</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>286</x>\n     <y>274</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>characterLineEdit</sender>\n   <signal>textChanged(QString)</signal>\n   <receiver>characterRadio</receiver>\n   <slot>click()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>583</x>\n     <y>86</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>179</x>\n     <y>78</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>accepted()</signal>\n   <receiver>AddGlyphDialog</receiver>\n   <slot>accept()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>378</x>\n     <y>522</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>8</x>\n     <y>344</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n</ui>\n"
  },
  {
    "path": "app/assets.qrc",
    "content": "<RCC>\n    <qresource prefix=\"/toolbar\">\n        <file>assets/code.svg</file>\n        <file>assets/copy.svg</file>\n        <file>assets/delete.svg</file>\n        <file>assets/paste.svg</file>\n        <file>assets/print.svg</file>\n        <file>assets/redo.svg</file>\n        <file>assets/reset.svg</file>\n        <file>assets/save.svg</file>\n        <file>assets/undo.svg</file>\n        <file>assets/font.svg</file>\n        <file>assets/clear.svg</file>\n        <file>assets/settings.svg</file>\n        <file>assets/open.svg</file>\n        <file>assets/add-glyph.svg</file>\n        <file>assets/delete-glyph.svg</file>\n    </qresource>\n    <qresource prefix=\"/icon\">\n        <file>assets/icon/fontedit256.png</file>\n        <file>assets/icon/fontedit96.png</file>\n        <file>assets/icon/fontedit96@2x.png</file>\n    </qresource>\n    <qresource prefix=\"/l10n\">\n        <file alias=\"fontedit_en.qm\">l10n/fontedit_en.qm</file>\n    </qresource>\n</RCC>\n"
  },
  {
    "path": "app/cmake_uninstall.cmake.in",
    "content": "if(NOT EXISTS \"@CMAKE_BINARY_DIR@/install_manifest.txt\")\n  message(FATAL_ERROR \"Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt\")\nendif(NOT EXISTS \"@CMAKE_BINARY_DIR@/install_manifest.txt\")\n\nfile(READ \"@CMAKE_BINARY_DIR@/install_manifest.txt\" files)\nstring(REGEX REPLACE \"\\n\" \";\" files \"${files}\")\nforeach(file ${files})\n  message(STATUS \"Uninstalling $ENV{DESTDIR}${file}\")\n  if(IS_SYMLINK \"$ENV{DESTDIR}${file}\" OR EXISTS \"$ENV{DESTDIR}${file}\")\n    exec_program(\n      \"@CMAKE_COMMAND@\" ARGS \"-E remove \\\"$ENV{DESTDIR}${file}\\\"\"\n      OUTPUT_VARIABLE rm_out\n      RETURN_VALUE rm_retval\n      )\n    if(NOT \"${rm_retval}\" STREQUAL 0)\n      message(FATAL_ERROR \"Problem when removing $ENV{DESTDIR}${file}\")\n    endif(NOT \"${rm_retval}\" STREQUAL 0)\n  else(IS_SYMLINK \"$ENV{DESTDIR}${file}\" OR EXISTS \"$ENV{DESTDIR}${file}\")\n    message(STATUS \"File $ENV{DESTDIR}${file} does not exist.\")\n  endif(IS_SYMLINK \"$ENV{DESTDIR}${file}\" OR EXISTS \"$ENV{DESTDIR}${file}\")\nendforeach(file)\n"
  },
  {
    "path": "app/command.h",
    "content": "#ifndef COMMAND_H\n#define COMMAND_H\n\n#include <QUndoCommand>\n#include \"facewidget.h\"\n#include \"mainwindowmodel.h\"\n\nclass Command : public QUndoCommand\n{\npublic:\n    Command(const QString& name,\n            std::function<void()> undo,\n            std::function<void()> redo,\n            QUndoCommand *parent = nullptr) :\n        QUndoCommand(name, parent),\n        undo_ { undo },\n        redo_ { redo }\n    {}\n\n    void undo() override { undo_(); }\n    void redo() override { redo_(); }\n\n    int id() const override { return -1; }\n\nprotected:\n    std::function<void()> undo_;\n    std::function<void()> redo_;\n};\n\n\nclass SwitchActiveGlyphCommand : public QUndoCommand\n{\npublic:\n    SwitchActiveGlyphCommand(FaceWidget* faceWidget,\n                             MainWindowModel* viewModel,\n                             std::size_t fromIndex,\n                             std::size_t toIndex,\n                             QUndoCommand *parent = nullptr) :\n        QUndoCommand(QObject::tr(\"Switch Active Glyph\"), parent),\n        faceWidget_ { faceWidget },\n        viewModel_ { viewModel },\n        fromIndex_ { fromIndex },\n        toIndex_ { toIndex }\n    {}\n\n    void undo() override {\n        faceWidget_->setCurrentGlyphIndex(fromIndex_);\n        viewModel_->setActiveGlyphIndex(fromIndex_);\n    }\n\n    void redo() override {\n        faceWidget_->setCurrentGlyphIndex(toIndex_);\n        viewModel_->setActiveGlyphIndex(toIndex_);\n    }\n\n    bool isObsolete() const {\n        return fromIndex_ == toIndex_;\n    }\n\n    int id() const override { return 0xa5b939e9; }\n\n    bool mergeWith(const QUndoCommand *other) override {\n        if (other->id() != id())\n            return false;\n\n        auto otherCommand = static_cast<const SwitchActiveGlyphCommand *>(other);\n\n        toIndex_ = otherCommand->toIndex_;\n        return true;\n    }\n\n    void setToIndex(std::size_t index) {\n        toIndex_ = index;\n    }\n\nprivate:\n    FaceWidget* faceWidget_;\n    MainWindowModel* viewModel_;\n    std::size_t fromIndex_;\n    std::size_t toIndex_;\n};\n\n#endif // COMMAND_H\n"
  },
  {
    "path": "app/common/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.9)\n\nset(CMAKE_AUTOUIC OFF)\nset(CMAKE_AUTOMOC OFF)\nset(CMAKE_AUTORCC OFF)\n\nproject(common LANGUAGES CXX)\n\nfind_package(Qt5 COMPONENTS Core Gui Widgets REQUIRED)\n\nadd_library(common\n    common.h\n    f2b_qt_compat.cpp\n    f2b_qt_compat.h)\n\ntarget_include_directories(common PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})\n\ntarget_link_libraries(common PRIVATE Qt5::Widgets Qt5::Core font2bytes)\n"
  },
  {
    "path": "app/common/common.h",
    "content": "#ifndef COMMON_H\n#define COMMON_H\n\n#include <QRgb>\n\nnamespace Color {\n\nstatic constexpr QRgb activeGlyph = 0xff000000;\nstatic constexpr QRgb inactiveGlyph = 0xffe2e2e2;\nstatic constexpr QRgb glyphMargin = 0xffe2e2e2;\n\nstatic constexpr QRgb inactiveText = 0xffc8c8c8;\n\n}\n\n#if defined(Q_OS_MAC)\n    static const QString consoleFontName = \"Monaco\";\n#elif defined(Q_OS_WIN)\n    static const QString consoleFontName = \"Consolas\";\n#else\n    static const QString consoleFontName = \"Monaco\";\n#endif\n\n\n#endif // COMMON_H\n"
  },
  {
    "path": "app/common/f2b_qt_compat.cpp",
    "content": "#include \"f2b_qt_compat.h\"\n\nstatic constexpr quint32 font_glyph_magic_number = 0x92588c12;\nstatic constexpr quint32 font_face_magic_number = 0x03f59a82;\n\nstatic constexpr quint32 font_glyph_version = 1;\nstatic constexpr quint32 font_face_version = 2;\n\nusing namespace f2b;\n\nQDataStream& operator<<(QDataStream& s, const font::glyph& glyph)\n{\n    s << font_glyph_magic_number;\n    s << font_glyph_version;\n    s.setVersion(QDataStream::Qt_5_7);\n    s << (quint32) glyph.size().width;\n    s << (quint32) glyph.size().height;\n    s << glyph.pixels();\n\n    return s;\n}\n\nQDataStream& operator>>(QDataStream& s, font::glyph& glyph)\n{\n    quint32 magic_number;\n    quint32 version;\n    s >> magic_number >> version;\n    if (magic_number == font_glyph_magic_number && version == font_glyph_version) {\n        s.setVersion(QDataStream::Qt_5_7);\n        quint32 width, height;\n        s >> width >> height;\n\n        std::vector<bool> pixels;\n        pixels.reserve(width * height);\n        s >> pixels;\n\n        glyph = font::glyph({width, height}, pixels);\n    }\n\n    return s;\n}\n\nQDataStream& operator<<(QDataStream& s, const font::face& face)\n{\n    s << font_face_magic_number;\n    s << font_face_version;\n    s.setVersion(QDataStream::Qt_5_7);\n    s << (quint32) face.glyphs_size().width;\n    s << (quint32) face.glyphs_size().height;\n    s << face.glyphs();\n    s << face.exported_glyph_ids();\n\n    return s;\n\n}\n\nQDataStream& operator>>(QDataStream& s, font::face& face)\n{\n    quint32 magic_number;\n    quint32 version;\n    s >> magic_number >> version;\n    if (magic_number == font_face_magic_number) {\n        s.setVersion(QDataStream::Qt_5_7);\n        quint32 width, height;\n        s >> width >> height;\n\n        std::vector<font::glyph> glyphs;\n        s >> glyphs;\n\n        std::set<std::size_t> exported_glyph_ids;\n        if (version < 2) {\n            for (std::size_t i = 0; i < glyphs.size(); i++) {\n                exported_glyph_ids.insert(i);\n            }\n        } else {\n            s >> exported_glyph_ids;\n        }\n        face = font::face({width, height}, glyphs, exported_glyph_ids);\n    }\n\n    return s;\n}\n\nQVariant to_qvariant(const source_code::indentation& i) {\n    if (std::holds_alternative<source_code::tab>(i)) {\n        return QVariant(-1);\n    } else if (std::holds_alternative<source_code::space>(i)) {\n        return QVariant((uint)std::get<source_code::space>(i).num_spaces);\n    }\n    return QVariant();\n}\n\nsource_code::indentation from_qvariant(const QVariant& v) {\n    bool ok;\n    auto intValue = v.toInt(&ok);\n    if (ok && intValue == -1) {\n        return source_code::tab {};\n    }\n    auto uintValue = v.toUInt(&ok);\n    if (ok) {\n        return source_code::space { uintValue };\n    }\n    return source_code::tab {};\n}\n"
  },
  {
    "path": "app/common/f2b_qt_compat.h",
    "content": "#ifndef F2B_QT_COMPAT_H\n#define F2B_QT_COMPAT_H\n\n#include \"f2b.h\"\n#include <QPoint>\n#include <QSize>\n#include <QBitmap>\n#include <QPainter>\n#include <QDebug>\n#include \"common.h\"\n\n#include <QDataStream>\n#include <optional>\n#include <vector>\n#include <unordered_map>\n#include <set>\n\nnamespace f2b {\n\nnamespace font {\n\ninline font::point point_with_qpoint(const QPoint &p)\n{\n    return { static_cast<std::size_t>(qMax(0, p.x())),\n                static_cast<std::size_t>(qMax(0, p.y())) };\n}\n\ninline QPoint qpoint_with_point(const font::point &p)\n{\n    return QPoint { static_cast<int>(p.x), static_cast<int>(p.y) };\n}\n\ninline font::glyph_size size_with_qsize(const QSize &s)\n{\n    return { static_cast<std::size_t>(qMax(0, s.width())),\n                static_cast<std::size_t>(qMax(0, s.height())) };\n}\n\ninline QSize qsize_with_size(const font::glyph_size &s)\n{\n    return QSize { static_cast<int>(s.width), static_cast<int>(s.height) };\n}\n\ninline QImage glyph_preview_image(const font::glyph &g, font::margins m)\n{\n    auto useful_glyph_size = g.size();\n    useful_glyph_size.height -= m.top + m.bottom;\n\n    auto useful_image_size = qsize_with_size(useful_glyph_size);\n\n    QImage image(useful_image_size, QImage::Format_Mono);\n    image.fill(1);\n\n    for (std::vector<bool>::size_type y = 0; y < useful_glyph_size.height; ++y) {\n        for (std::vector<bool>::size_type x = 0; x < useful_glyph_size.width; ++x) {\n            if (g.is_pixel_set({x, y + m.top})) {\n                image.setPixel(x, y, 0);\n            }\n        }\n    }\n\n    return image;\n}\n\n} // namespace Font\n\n} // namespace f2b\n\n\nstatic constexpr quint32 std_optional_magic_number = 0x46b13680;\nstatic constexpr quint32 std_vector_magic_number = 0x30612113;\nstatic constexpr quint32 std_set_magic_number = 0x254c2e1e;\nstatic constexpr quint32 std_unordered_map_magic_number = 0xc9eb6edf;\n\nstatic constexpr quint32 std_optional_version = 1;\nstatic constexpr quint32 std_vector_version = 1;\nstatic constexpr quint32 std_set_version = 1;\nstatic constexpr quint32 std_unordered_map_version = 1;\n\ntemplate<typename T>\ninline QDataStream& operator<<(QDataStream& s, const std::optional<T>& opt)\n{\n    s << std_optional_magic_number;\n    s << std_optional_version;\n    s.setVersion(QDataStream::Qt_5_7);\n    if (opt.has_value()) {\n        s << true << QVariant(opt.value());\n    } else {\n        s << false;\n    }\n    return s;\n}\n\ntemplate<typename T>\ninline QDataStream& operator>>(QDataStream& s, std::optional<T>& opt)\n{\n    quint32 magic_number;\n    quint32 version;\n    s >> magic_number >> version;\n    if (magic_number == std_optional_magic_number && version == std_optional_version) {\n        s.setVersion(QDataStream::Qt_5_7);\n        bool has_value;\n        s >> has_value;\n\n        QVariant value;\n\n        if (has_value) {\n            s >> value;\n            opt = value.value<T>();\n        } else {\n            opt = {};\n        }\n    }\n\n    return s;\n}\n\n\ntemplate<typename T>\ninline QDataStream& operator<<(QDataStream& s, const std::vector<T>& vec)\n{\n    s << std_vector_magic_number;\n    s << std_vector_version;\n    s.setVersion(QDataStream::Qt_5_7);\n    s << (quint32) vec.size();\n\n    for (const auto& value : vec) {\n        s << value;\n    }\n\n    return s;\n}\n\ntemplate<typename T>\ninline QDataStream& operator>>(QDataStream& s, std::vector<T>& vec)\n{\n    quint32 magic_number;\n    quint32 version;\n    s >> magic_number >> version;\n    if (magic_number == std_vector_magic_number && version == std_vector_version) {\n        s.setVersion(QDataStream::Qt_5_7);\n        quint32 size;\n        s >> size;\n\n        vec.clear();\n        vec.reserve(size);\n\n        T value;\n        for (quint32 i = 0; i < size; ++i) {\n            s >> value;\n            vec.push_back(value);\n        }\n    }\n    return s;\n}\n\n\ntemplate<typename T>\ninline QDataStream& operator<<(QDataStream& s, const std::set<T>& set)\n{\n    s << std_set_magic_number;\n    s << std_set_version;\n    s.setVersion(QDataStream::Qt_5_7);\n    s << (quint32) set.size();\n\n    for (const auto& value : set) {\n        s << value;\n    }\n\n    return s;\n}\n\ntemplate<typename T>\ninline QDataStream& operator>>(QDataStream& s, std::set<T>& set)\n{\n    quint32 magic_number;\n    quint32 version;\n    s >> magic_number >> version;\n    if (magic_number == std_set_magic_number && version == std_set_version) {\n        s.setVersion(QDataStream::Qt_5_7);\n        quint32 size;\n        s >> size;\n\n        set.clear();\n\n        T value;\n        for (quint32 i = 0; i < size; ++i) {\n            s >> value;\n            set.insert(value);\n        }\n    }\n    return s;\n}\n\ntemplate<>\ninline QDataStream& operator<<(QDataStream& s, const std::set<std::size_t>& set)\n{\n    s << std_set_magic_number;\n    s << std_set_version;\n    s.setVersion(QDataStream::Qt_5_7);\n    s << (quint32) set.size();\n\n    for (const auto& value : set) {\n        s << (quint32) value;\n    }\n\n    return s;\n}\n\ntemplate<>\ninline QDataStream& operator>>(QDataStream& s, std::set<std::size_t>& set)\n{\n    quint32 magic_number;\n    quint32 version;\n    s >> magic_number >> version;\n    if (magic_number == std_set_magic_number && version == std_set_version) {\n        s.setVersion(QDataStream::Qt_5_7);\n        quint32 size;\n        s >> size;\n\n        set.clear();\n\n        quint32 value;\n        for (quint32 i = 0; i < size; ++i) {\n            s >> value;\n            set.insert(static_cast<std::size_t>(value));\n        }\n    }\n    return s;\n}\n\n\ntemplate<typename K, typename V>\ninline QDataStream& operator<<(QDataStream& s, const std::unordered_map<K, V>& map)\n{\n    s << std_unordered_map_magic_number;\n    s << std_unordered_map_version;\n    s.setVersion(QDataStream::Qt_5_7);\n    s << (quint32) map.size();\n\n    for (const auto& [k,v] : map) {\n        s << k;\n        s << v;\n    }\n\n    return s;\n}\n\ntemplate<typename V>\ninline QDataStream& operator<<(QDataStream& s, const std::unordered_map<std::size_t, V>& map)\n{\n    s << std_unordered_map_magic_number;\n    s << std_unordered_map_version;\n    s.setVersion(QDataStream::Qt_5_7);\n    s << (quint32) map.size();\n\n    for (const auto& [k,v] : map) {\n        s << (quint32) k;\n        s << v;\n    }\n\n    return s;\n}\n\ntemplate<typename K, typename V>\ninline QDataStream& operator>>(QDataStream& s, std::unordered_map<K, V>& map)\n{\n    quint32 magic_number;\n    quint32 version;\n    s >> magic_number >> version;\n    if (magic_number == std_unordered_map_magic_number && version == std_unordered_map_version) {\n        s.setVersion(QDataStream::Qt_5_7);\n        quint32 size;\n        s >> size;\n\n        map.clear();\n        map.reserve(size);\n\n        K key;\n        V value;\n        for (quint32 i = 0; i < size; ++i) {\n            s >> key;\n            s >> value;\n            map[key] = value;\n        }\n    }\n    return s;\n}\n\ntemplate<typename V>\ninline QDataStream& operator>>(QDataStream& s, std::unordered_map<std::size_t, V>& map)\n{\n    quint32 magic_number;\n    quint32 version;\n    s >> magic_number >> version;\n    if (magic_number == std_unordered_map_magic_number && version == std_unordered_map_version) {\n        s.setVersion(QDataStream::Qt_5_7);\n        quint32 size;\n        s >> size;\n\n        map.clear();\n        map.reserve(size);\n\n        quint32 key;\n        V value;\n        for (quint32 i = 0; i < size; ++i) {\n            s >> key;\n            s >> value;\n            map[static_cast<std::size_t>(key)] = value;\n        }\n    }\n    return s;\n}\n\nQDataStream& operator<<(QDataStream& s, const f2b::font::glyph& glyph);\nQDataStream& operator>>(QDataStream& s, f2b::font::glyph& glyph);\n\nQDataStream& operator<<(QDataStream& s, const f2b::font::face& face);\nQDataStream& operator>>(QDataStream& s, f2b::font::face& face);\n\n\nQVariant to_qvariant(const f2b::source_code::indentation& i);\nf2b::source_code::indentation from_qvariant(const QVariant& v);\n\n#endif // F2B_QT_COMPAT_H\n"
  },
  {
    "path": "app/fontfaceviewmodel.cpp",
    "content": "#include \"fontfaceviewmodel.h\"\n#include \"f2b.h\"\n#include \"f2b_qt_compat.h\"\n#include \"qfontfacereader.h\"\n\n#include <utility>\n#include <stdexcept>\n#include <cassert>\n\n#include <QDebug>\n#include <QPalette>\n#include <QFileInfo>\n\n\nf2b::font::face import_face(const QFont &font)\n{\n    QFontFaceReader adapter(font);\n    return f2b::font::face(adapter);\n}\n\nQString font_name(const QFont &font)\n{\n    QStringList s;\n    s << QString(\"%1 %2pt\").arg(font.family()).arg(QString::number(font.pointSize()));\n    if (font.bold()) {\n        s << \"Bold\";\n    }\n    if (font.italic()) {\n        s << \"Italic\";\n    }\n    if (font.underline()) {\n        s << \"Underline\";\n    }\n    if (font.strikeOut()) {\n        s << \"Strikeout\";\n    }\n\n    return s.join(\", \");\n}\n\n\nFontFaceViewModel::FontFaceViewModel(const QString& documentFilePath)\n{\n    QFile f(documentFilePath);\n    if (!f.exists() || !f.permissions().testFlag(QFileDevice::ReadUser)) {\n        throw std::runtime_error { \"Unable to open file \" + documentFilePath.toStdString() };\n    }\n\n    f.open(QIODevice::ReadOnly);\n    QDataStream s(&f);\n    s >> *this;\n    f.close();\n    isDirty_ = false;\n}\n\nFontFaceViewModel::FontFaceViewModel(f2b::font::face face, std::optional<QString> name) noexcept :\n    face_ { face },\n    name_ { name },\n    originalMargins_ { face.calculate_margins() }\n{\n}\n\nFontFaceViewModel::FontFaceViewModel(const QFont &font) :\n    FontFaceViewModel(import_face(font), font_name(font))\n{\n    font_ = font;\n    isDirty_ = true;\n}\n\nvoid FontFaceViewModel::saveToFile(const QString &documentPath)\n{\n    QFile f(documentPath);\n    QFile directory(QFileInfo(documentPath).path());\n\n    if (!directory.permissions().testFlag(QFileDevice::WriteUser) ||\n            (f.exists() && !f.permissions().testFlag(QFileDevice::WriteUser)))\n    {\n        throw std::runtime_error { \"Unable to write to file: \" + documentPath.toStdString() };\n    }\n\n    f.open(QIODevice::WriteOnly);\n    QDataStream s(&f);\n    s << *this;\n    f.close();\n    isDirty_ = false;\n}\n\nFaceInfo FontFaceViewModel::faceInfo() const\n{\n    auto fontName = name_.has_value() ? name_.value() : QObject::tr(\"Custom font\");\n    auto size = face_.glyphs_size();\n    size.height -= originalMargins_.top + originalMargins_.bottom;\n    return { fontName, face_.glyphs_size(), size, face_.num_glyphs(), face_.exported_glyph_ids().size() };\n}\n\nvoid FontFaceViewModel::modifyGlyph(std::size_t index, const f2b::font::glyph &new_glyph)\n{\n    doModifyGlyph(index, [&](f2b::font::glyph &glyph) {\n        glyph = new_glyph;\n    });\n}\n\nvoid FontFaceViewModel::modifyGlyph(std::size_t index,\n                                     const BatchPixelChange &change,\n                                     BatchPixelChange::ChangeType changeType)\n{\n    doModifyGlyph(index, [&](f2b::font::glyph& glyph) {\n        change.apply(glyph, changeType);\n    });\n}\n\nvoid FontFaceViewModel::doModifyGlyph(std::size_t idx, std::function<void (f2b::font::glyph&)> change)\n{\n    f2b::font::glyph& glyph { face_.glyph_at(idx) };\n    bool first_change = false;\n\n    if (originalGlyphs_.count(idx) == 0) {\n        qDebug() << \"active_glyph non-const cache miss\";\n        originalGlyphs_.insert({ idx, glyph });\n        first_change = true;\n    } else {\n        qDebug() << \"active_glyph non-const cache hit\";\n    }\n\n    change(glyph);\n    isDirty_ = true;\n\n    // remove glyph from originals when restoring initial state\n    if (!first_change && glyph == originalGlyphs_.at(idx)) {\n        originalGlyphs_.erase(idx);\n    }\n}\n\nvoid FontFaceViewModel::reset()\n{\n    face_ = originalFace();\n    originalGlyphs_.clear();\n}\n\nvoid FontFaceViewModel::resetGlyph(std::size_t index)\n{\n    if (isGlyphModified(index)) {\n        face_.set_glyph(originalGlyphs_.at(index), index);\n        originalGlyphs_.erase(activeGlyphIndex_.value());\n        isDirty_ = true;\n    }\n}\n\nvoid FontFaceViewModel::appendGlyph(f2b::font::glyph newGlyph)\n{\n    face_.append_glyph(std::move(newGlyph));\n    isDirty_ = true;\n}\n\nvoid FontFaceViewModel::deleteGlyph(std::size_t index)\n{\n    if (index == face_.num_glyphs() - 1) {\n        if (activeGlyphIndex_.has_value() && activeGlyphIndex_.value() == face_.num_glyphs() - 1) {\n            activeGlyphIndex_ = face_.num_glyphs() - 2;\n        }\n        face_.delete_last_glyph();\n    } else {\n        face_.clear_glyph(index);\n    }\n    isDirty_ = true;\n}\n\nf2b::font::face FontFaceViewModel::originalFace() const noexcept\n{\n    f2b::font::face f = face_;\n    for (const auto& pair : originalGlyphs_) {\n        f.set_glyph(pair.second, pair.first);\n    }\n    return f;\n}\n\nstatic constexpr auto fontfaceviewmodel_magic_number = 0x1c22f998;\nstatic constexpr auto fontfaceviewmodel_version = 2;\n\nQDataStream& operator<<(QDataStream& s, const FontFaceViewModel &vm)\n{\n    s << (quint32) fontfaceviewmodel_magic_number;\n    s << (qint32) fontfaceviewmodel_version;\n    s.setVersion(QDataStream::Qt_5_7);\n\n    s << vm.face_;\n    s << vm.name_;\n    s << (quint32) vm.originalMargins_.top << (quint32) vm.originalMargins_.bottom;\n\n    s << vm.font_;\n    return s;\n}\n\nQDataStream& operator>>(QDataStream& s, FontFaceViewModel& vm)\n{\n    quint32 magic_number;\n    quint32 version;\n    s >> magic_number >> version;\n    if (magic_number == fontfaceviewmodel_magic_number && version <= fontfaceviewmodel_version) {\n        s.setVersion(QDataStream::Qt_5_7);\n\n        s >> vm.face_;\n        s >> vm.name_;\n\n        quint32 top, bottom;\n        s >> top >> bottom;\n        vm.originalMargins_ = { top, bottom };\n        vm.originalGlyphs_ = {};\n        vm.activeGlyphIndex_ = {};\n\n        if (version <= 2) {\n            s >> vm.font_;\n        }\n    }\n\n    return s;\n}\n"
  },
  {
    "path": "app/fontfaceviewmodel.h",
    "content": "#ifndef FONTFACEVIEWMODEL_H\n#define FONTFACEVIEWMODEL_H\n\n#include <QFont>\n#include \"f2b.h\"\n#include <optional>\n#include <vector>\n#include <exception>\n#include <unordered_map>\n#include \"batchpixelchange.h\"\n\nstruct FaceInfo\n{\n    QString fontName;\n    f2b::font::glyph_size size;\n    f2b::font::glyph_size sizeWithoutMargins;\n    std::size_t numberOfGlyphs;\n    std::size_t numberOfExportedGlyphs;\n};\n\nclass FontFaceViewModel\n{\npublic:\n    explicit FontFaceViewModel() = default;\n    explicit FontFaceViewModel(const QString& documentPath);\n    explicit FontFaceViewModel(f2b::font::face face, std::optional<QString> name) noexcept;\n    explicit FontFaceViewModel(const QFont& font);\n\n    void saveToFile(const QString& documentPath);\n\n    std::optional<QFont> font() const noexcept { return font_; }\n\n    const f2b::font::face& face() const noexcept { return face_; }\n    f2b::font::face& face() noexcept { return face_; }\n\n    FaceInfo faceInfo() const;\n\n    f2b::font::face originalFace() const noexcept;\n    f2b::font::margins originalFaceMargins() const noexcept { return originalMargins_; }\n\n    void setGlyphExportedState(std::size_t idx, bool isExported) {\n        if (idx >= face_.num_glyphs()) {\n            throw std::out_of_range(\"Active glyph index higher than number of glyphs.\");\n        }\n        if (isExported) {\n            face_.exported_glyph_ids().insert(idx);\n        } else {\n            face_.exported_glyph_ids().erase(idx);\n        }\n        isDirty_ = true;\n    }\n\n    void setActiveGlyphIndex(std::optional<std::size_t> idx) {\n        if (idx.has_value() && idx.value() >= face_.num_glyphs()) {\n            throw std::out_of_range(\"Active glyph index higher than number of glyphs.\");\n        }\n        activeGlyphIndex_ = idx;\n    }\n\n    std::optional<std::size_t> activeGlyphIndex() const noexcept {\n        return activeGlyphIndex_;\n    }\n\n    std::optional<f2b::font::glyph> activeGlyph() const {\n        if (activeGlyphIndex_.has_value()) {\n            return face_.glyph_at(activeGlyphIndex_.value());\n        }\n        return {};\n    }\n\n    void resetActiveGlyph() {\n        if (!activeGlyphIndex_.has_value()) {\n            return;\n        }\n        resetGlyph(activeGlyphIndex_.value());\n    }\n\n    void reset();\n\n    void resetGlyph(std::size_t index);\n    void modifyGlyph(std::size_t index, const f2b::font::glyph& new_glyph);\n    void modifyGlyph(std::size_t index, const BatchPixelChange& change,\n                      BatchPixelChange::ChangeType changeType = BatchPixelChange::ChangeType::Normal);\n    void appendGlyph(f2b::font::glyph newGlyph);\n    void deleteGlyph(std::size_t index);\n\n    bool isModified() const {\n        return originalGlyphs_.size() > 0;\n    }\n\n    bool isGlyphModified(std::size_t idx) const {\n        // modified glyphs have their unmodified counterparts stored in originalGlyphs_.\n        return originalGlyphs_.count(idx) == 1;\n    }\n\n    bool isModifiedSinceSave() const {\n        return isDirty_;\n    }\n\nprivate:\n    void doModifyGlyph(std::size_t idx, std::function<void(f2b::font::glyph&)> change);\n\n    f2b::font::face face_;\n    std::optional<QString> name_;\n    f2b::font::margins originalMargins_;\n    // this holds copies of unmodified glyphs once they are edited.\n    std::unordered_map<std::size_t, f2b::font::glyph> originalGlyphs_;\n    std::optional<QFont> font_;\n\n    // not persisted\n    std::optional<std::size_t> activeGlyphIndex_;\n    bool isDirty_ { false };\n\n    friend QDataStream& operator<<(QDataStream&, const FontFaceViewModel&);\n    friend QDataStream& operator>>(QDataStream& s, FontFaceViewModel& vm);\n};\n\nQDataStream& operator<<(QDataStream& s, const FontFaceViewModel& vm);\nQDataStream& operator>>(QDataStream& s, FontFaceViewModel& vm);\n\n#endif // FONTFACEVIEWMODEL_H\n"
  },
  {
    "path": "app/global.h",
    "content": "#ifndef GLOBAL_H\n#define GLOBAL_H\n\n#include <QString>\n\nnamespace Global {\nstatic const QString organization_name = \"Dominik Kapusta\";\nstatic const QString organization_domain = \"kapusta.cc\";\nstatic const QString application_name = \"FontEdit\";\nstatic constexpr std::string_view application_version = VERSION;\n}\n\n\n#endif // GLOBAL_H\n"
  },
  {
    "path": "app/l10n/fontedit_en.ts",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"en_US\">\n<context>\n    <name>AboutDialog</name>\n    <message>\n        <location filename=\"../ui/aboutdialog.ui\" line=\"14\"/>\n        <source>About FontEdit</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../ui/aboutdialog.ui\" line=\"75\"/>\n        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:18pt;&quot;&gt;FontEdit &lt;/span&gt;&lt;span style=&quot; font-size:13pt; font-weight:400;&quot;&gt;v##version## build ##build##&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-size:13pt; font-weight:400;&quot;&gt;Copyright ##year## Dominik Kapusta&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://github.com/ayoy/fontedit&quot;&gt;&lt;span style=&quot; font-size:13pt; font-weight:400; text-decoration: underline; color:#0068da;&quot;&gt;Get Source Code&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:13pt; font-weight:400;&quot;&gt;•&lt;/span&gt;&lt;a href=&quot;https://github.com/ayoy/fontedit/issues&quot;&gt;&lt;span style=&quot; font-size:13pt; font-weight:400; text-decoration: underline; color:#0068da;&quot;&gt;Report a Bug&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-size:13pt; font-weight:400;&quot;&gt;This program is distributed under the terms of &lt;/span&gt;&lt;a href=&quot;https://www.gnu.org/licenses/gpl-3.0.en.html&quot;&gt;&lt;span style=&quot; font-size:13pt; font-weight:400; text-decoration: underline; color:#0068da;&quot;&gt;General Public License v3&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:13pt;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-size:13pt; font-weight:400;&quot;&gt;&lt;br/&gt;Icons made by &lt;/span&gt;&lt;a href=&quot;https://www.flaticon.com/authors/smashicons&quot;&gt;&lt;span style=&quot; font-size:13pt; font-weight:400; text-decoration: underline; color:#0068da;&quot;&gt;Smashicons&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:13pt; font-weight:400;&quot;&gt; and &lt;/span&gt;&lt;a href=&quot;https://www.flaticon.com/authors/freepik&quot;&gt;&lt;span style=&quot; font-size:13pt; font-weight:400; text-decoration: underline; color:#0068da;&quot;&gt;Freepik&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:13pt; font-weight:400;&quot;&gt; from &lt;/span&gt;&lt;a href=&quot;https://www.flaticon.com/&quot;&gt;&lt;span style=&quot; font-size:13pt; font-weight:400; text-decoration: underline; color:#0068da;&quot;&gt;www.flaticon.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:13pt; font-weight:400;&quot;&gt;.&lt;/span&gt;&lt;a href=&quot;https://www.flaticon.com/&quot;&gt;&lt;span style=&quot; font-size:13pt; font-weight:400; text-decoration: underline; color:#0068da;&quot;&gt;&lt;br/&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>\n        <translation></translation>\n    </message>\n</context>\n<context>\n    <name>AddGlyphDialog</name>\n    <message>\n        <location filename=\"../addglyphdialog.ui\" line=\"20\"/>\n        <source>Add New Glyph</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../addglyphdialog.ui\" line=\"29\"/>\n        <source>Adding Glyph for </source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../addglyphdialog.ui\" line=\"38\"/>\n        <source>Add empty Glyph</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../addglyphdialog.ui\" line=\"55\"/>\n        <source>Initialize with font character:</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../addglyphdialog.ui\" line=\"65\"/>\n        <source>Current document font not found in system</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../addglyphdialog.ui\" line=\"78\"/>\n        <source>Type character here</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../addglyphdialog.ui\" line=\"102\"/>\n        <source>Copy existing Glyph:</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../addglyphdialog.cpp\" line=\"15\"/>\n        <source>Add a new Glyph to %1</source>\n        <translation></translation>\n    </message>\n</context>\n<context>\n    <name>GlyphInfoWidget</name>\n    <message>\n        <location filename=\"../ui/glyphinfowidget.cpp\" line=\"34\"/>\n        <source>Exported</source>\n        <translation></translation>\n    </message>\n</context>\n<context>\n    <name>MainWindow</name>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"32\"/>\n        <source>Edit Font</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"72\"/>\n        <source>Ctrl+R</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"266\"/>\n        <source>TextLabel</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"298\"/>\n        <source>Source Code</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"334\"/>\n        <source>Updating source code...</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"350\"/>\n        <source>Output Format</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"388\"/>\n        <source>Misc Options</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"394\"/>\n        <source>Invert Bits</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"401\"/>\n        <source>Reverse Bits (MSB)</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"465\"/>\n        <source>Print</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"14\"/>\n        <source>FontEdit</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"240\"/>\n        <source>Toggle exported state by pressing Space on a selected glyph</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"243\"/>\n        <location filename=\"../mainwindow.ui\" line=\"702\"/>\n        <source>Show non-exported Glyphs</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"359\"/>\n        <source>Export Selected Glyphs</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"369\"/>\n        <source>Export All Glyphs</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"408\"/>\n        <source>Include Line Spacing</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"418\"/>\n        <source>Font Array Name</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"430\"/>\n        <source>font</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"440\"/>\n        <source>Indentation</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"472\"/>\n        <source>Export Source Code...</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"495\"/>\n        <source>File</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"511\"/>\n        <source>Edit</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"524\"/>\n        <source>Help</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"531\"/>\n        <source>View</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"543\"/>\n        <source>New</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"546\"/>\n        <location filename=\"../mainwindow.ui\" line=\"558\"/>\n        <source>Ctrl+N</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"555\"/>\n        <source>Import Font</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"567\"/>\n        <location filename=\"../mainwindow.cpp\" line=\"668\"/>\n        <source>Reset Glyph</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"576\"/>\n        <source>Export...</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"585\"/>\n        <location filename=\"../mainwindow.cpp\" line=\"492\"/>\n        <source>Save</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"588\"/>\n        <source>Ctrl+S</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"597\"/>\n        <location filename=\"../mainwindow.cpp\" line=\"684\"/>\n        <source>Reset Font</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"606\"/>\n        <source>Print...</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"609\"/>\n        <source>Ctrl+P</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"614\"/>\n        <source>Save As...</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"617\"/>\n        <source>Ctrl+Shift+S</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"626\"/>\n        <location filename=\"../mainwindow.cpp\" line=\"397\"/>\n        <source>Add Glyph</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"629\"/>\n        <source>Ctrl+Shift+N</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"634\"/>\n        <source>Quit</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"637\"/>\n        <source>Ctrl+Q</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"646\"/>\n        <source>Copy Glyph</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"655\"/>\n        <source>Paste Glyph</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"664\"/>\n        <source>Open...</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"667\"/>\n        <source>Ctrl+O</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"672\"/>\n        <source>Recent files</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"677\"/>\n        <source>Close</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"682\"/>\n        <source>About FontEdit</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"691\"/>\n        <location filename=\"../mainwindow.cpp\" line=\"415\"/>\n        <location filename=\"../mainwindow.cpp\" line=\"425\"/>\n        <source>Delete Glyph</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.ui\" line=\"707\"/>\n        <source>Check for Updates...</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"204\"/>\n        <source>Check for updates at start-up</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"213\"/>\n        <source>Update Available</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"214\"/>\n        <source>FontEdit %1 is available (you have %2).\nGet the new version from GitHub Releases Page.</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"218\"/>\n        <source>Go to Releases Page</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"219\"/>\n        <source>Dismiss</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"228\"/>\n        <source>No Update Available</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"229\"/>\n        <source>You&apos;re using the latest version of FontEdit</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"300\"/>\n        <source>Start by importing a system font or opening an existing document</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"303\"/>\n        <source>Select a glyph on the right to edit it</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"306\"/>\n        <source>Click and drag to paint, hold Alt or Ctrl to erase.</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"347\"/>\n        <source>Select Font</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"366\"/>\n        <source>Open Document</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"416\"/>\n        <source>You can only delete Glyphs at the end of the list. This Glyph will be cleared instead of deleted (to ensure that other Glyphs&apos; indexes are unchanged).</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"425\"/>\n        <source>Clear Glyph</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"462\"/>\n        <source>Save Document</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"470\"/>\n        <source>Error</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"492\"/>\n        <source>Don&apos;t Save</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"492\"/>\n        <source>Cancel</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"495\"/>\n        <source>Do you want to save the changes you made? Your changes will be lost if you don&apos;t save them.</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"543\"/>\n        <source>Toggle Glyph Exported</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"589\"/>\n        <source>Size (full): %1x%2px</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"590\"/>\n        <source>Size (adjusted): %1x%2px</source>\n        <translation></translation>\n    </message>\n    <message numerus=\"yes\">\n        <location filename=\"../mainwindow.cpp\" line=\"591\"/>\n        <source>%n Glyph(s)</source>\n        <translation>\n            <numerusform>%n Glyph</numerusform>\n            <numerusform>%n Glyphs</numerusform>\n        </translation>\n    </message>\n    <message numerus=\"yes\">\n        <location filename=\"../mainwindow.cpp\" line=\"592\"/>\n        <source>%n to export</source>\n        <translation>\n            <numerusform>%n to export</numerusform>\n            <numerusform>%n to export</numerusform>\n        </translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"635\"/>\n        <source>Edit Glyph</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"683\"/>\n        <source>Are you sure you want to reset all changes to the font? This operation cannot be undone.</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"746\"/>\n        <source>Save Source Code</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"761\"/>\n        <source>Source code successfully exported.</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindow.cpp\" line=\"763\"/>\n        <source>Unable to write to file: </source>\n        <translation></translation>\n    </message>\n</context>\n<context>\n    <name>MainWindowModel</name>\n    <message>\n        <location filename=\"../mainwindowmodel.cpp\" line=\"68\"/>\n        <source>Tab</source>\n        <translation></translation>\n    </message>\n    <message numerus=\"yes\">\n        <location filename=\"../mainwindowmodel.cpp\" line=\"70\"/>\n        <source>%n Space(s)</source>\n        <translation>\n            <numerusform>%n Space</numerusform>\n            <numerusform>%n Spaces</numerusform>\n        </translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindowmodel.cpp\" line=\"145\"/>\n        <source>New Document</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../mainwindowmodel.cpp\" line=\"151\"/>\n        <source>Edited</source>\n        <translation></translation>\n    </message>\n</context>\n<context>\n    <name>QObject</name>\n    <message>\n        <location filename=\"../command.h\" line=\"39\"/>\n        <source>Switch Active Glyph</source>\n        <translation></translation>\n    </message>\n    <message>\n        <location filename=\"../fontfaceviewmodel.cpp\" line=\"90\"/>\n        <source>Custom font</source>\n        <translation></translation>\n    </message>\n</context>\n</TS>\n"
  },
  {
    "path": "app/macos/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>English</string>\n\t<key>CFBundleExecutable</key>\n\t<string>FontEdit</string>\n\t<key>CFBundleGetInfoString</key>\n\t<string></string>\n\t<key>CFBundleIconFile</key>\n\t<string>fontedit.icns</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>cc.kapusta.FontEdit</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleLongVersionString</key>\n\t<string></string>\n\t<key>CFBundleName</key>\n\t<string></string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n    <string>1.1.0</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n    <string>4</string>\n\t<key>CSResourcesFileMapped</key>\n\t<true/>\n    <key>LSMinimumSystemVersion</key>\n    <string>10.14</string>\n\t<key>NSHumanReadableCopyright</key>\n\t<string></string>\n\t<key>NSPrincipalClass</key>\n\t<string>NSApplication</string>\n\t<key>NSHighResolutionCapable</key>\n\t<string>True</string>\n    <key>NSRequiresAquaSystemAppearance</key>\n    <true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "app/main.cpp",
    "content": "#include \"mainwindow.h\"\n#include \"global.h\"\n\n#include <QApplication>\n#include <QtGui>\n\nint main(int argc, char *argv[])\n{\n    QApplication a(argc, argv);\n\n    QApplication::setOrganizationName(Global::organization_name);\n    QApplication::setOrganizationDomain(Global::organization_domain);\n    QApplication::setApplicationName(Global::application_name);\n    QApplication::setApplicationVersion(QString::fromStdString(std::string(Global::application_version)));\n\n    QTranslator myappTranslator;\n    myappTranslator.load(\":/l10n/fontedit_en.qm\");\n    a.installTranslator(&myappTranslator);\n\n    a.setAttribute(Qt::AA_UseHighDpiPixmaps);\n\n    MainWindow w;\n    w.show();\n    return QApplication::exec();\n}\n"
  },
  {
    "path": "app/mainwindow.cpp",
    "content": "#include \"mainwindow.h\"\n#include \"./ui_mainwindow.h\"\n#include \"f2b.h\"\n#include \"facewidget.h\"\n#include \"fontfaceviewmodel.h\"\n#include \"command.h\"\n#include \"aboutdialog.h\"\n#include \"addglyphdialog.h\"\n#include \"common.h\"\n\n#include <QGraphicsGridLayout>\n#include <QGraphicsWidget>\n#include <QDebug>\n#include <QStyle>\n#include <QFontDialog>\n#include <QFileDialog>\n#include <QScrollBar>\n#include <QMessageBox>\n#include <QKeySequence>\n#include <QElapsedTimer>\n#include <QStandardPaths>\n#include <QDesktopServices>\n\n#include <iostream>\n#include <stdexcept>\n\n#include <QFile>\n#include <QTextStream>\n\nstatic constexpr auto codeTabIndex = 1;\nstatic constexpr auto exportAllButtonIndex = -3;\nstatic constexpr auto fileFilter = \"FontEdit documents (*.fontedit)\";\n\nMainWindow::MainWindow(QWidget *parent)\n    : QMainWindow(parent),\n      ui_ { new Ui::MainWindow },\n      statusLabel_ { new QLabel() }\n{\n    ui_->setupUi(this);\n\n    initUI();\n    setupActions();\n    updateUI(viewModel_->uiState());\n\n    connectUpdateHelper();\n    connectUIInputs();\n    connectViewModelOutputs();\n    viewModel_->restoreSession();\n\n    ui_->statusBar->addPermanentWidget(statusLabel_);\n\n    updateHelper_->checkForUpdatesIfNeeded();\n}\n\nMainWindow::~MainWindow()\n{\n    delete ui_;\n}\n\nvoid MainWindow::connectUpdateHelper()\n{\n    connect(updateHelper_.get(), &UpdateHelper::updateAvailable,\n            [&] (UpdateHelper::Update update) {\n        showUpdateDialog(update);\n    });\n    connect(updateHelper_.get(), &UpdateHelper::updateNotAvailable,\n            [&] {\n        showUpdateDialog({});\n    });\n}\n\nvoid MainWindow::connectUIInputs()\n{\n    connect(ui_->actionAbout, &QAction::triggered, this, &MainWindow::showAboutDialog);\n    connect(ui_->actionImport_Font, &QAction::triggered, this, &MainWindow::showFontDialog);\n    connect(ui_->actionOpen, &QAction::triggered, this, &MainWindow::showOpenDocumentDialog);\n    connect(ui_->actionAdd_Glyph, &QAction::triggered, this, &MainWindow::showAddGlyphDialog);\n    connect(ui_->actionDelete_Glyph, &QAction::triggered, this, &MainWindow::showDeleteGlyphDialog);\n    connect(ui_->actionReset_Glyph, &QAction::triggered, this, &MainWindow::resetCurrentGlyph);\n    connect(ui_->actionReset_Font, &QAction::triggered, this, &MainWindow::resetFont);\n\n    connect(ui_->actionSave, &QAction::triggered, this, &MainWindow::save);\n    connect(ui_->actionSave_As, &QAction::triggered, this, &MainWindow::saveAs);\n    connect(ui_->actionClose, &QAction::triggered, this, &MainWindow::showCloseDocumentDialogIfNeeded);\n\n    connect(ui_->actionExport, &QAction::triggered, this, &MainWindow::exportSourceCode);\n    connect(ui_->exportButton, &QPushButton::clicked, this, &MainWindow::exportSourceCode);\n\n    connect(ui_->actionQuit, &QAction::triggered, this, &MainWindow::close);\n    connect(ui_->tabWidget, &QTabWidget::currentChanged, [&](int index) {\n        if (index == codeTabIndex) {\n            displaySourceCode();\n            viewModel_->registerInputEvent(UIState::InterfaceAction::ActionTabCode);\n        } else {\n            viewModel_->registerInputEvent(UIState::InterfaceAction::ActionTabEdit);\n        }\n    });\n    connect(ui_->exportMethodButtonGroup, QOverload<int>::of(&QButtonGroup::buttonClicked), [&](int buttonID) {\n        viewModel_->setExportAllEnabled(buttonID == exportAllButtonIndex);\n    });\n    connect(ui_->invertBitsCheckBox, &QCheckBox::stateChanged, [&](int state) {\n        viewModel_->setInvertBits(state == Qt::Checked);\n    });\n    connect(ui_->bitNumberingCheckBox, &QCheckBox::stateChanged, [&](int state) {\n        viewModel_->setMSBEnabled(state == Qt::Checked);\n    });\n    connect(ui_->lineSpacingCheckBox, &QCheckBox::stateChanged, [&](int state) {\n        viewModel_->setIncludeLineSpacing(state == Qt::Checked);\n    });\n    connect(ui_->formatComboBox, &QComboBox::currentTextChanged,\n            viewModel_.get(), &MainWindowModel::setOutputFormat);\n    connect(ui_->indentationComboBox, &QComboBox::currentTextChanged,\n            viewModel_.get(), &MainWindowModel::setIndentation);\n    connect(ui_->fontArrayNameEdit, &QLineEdit::textChanged, [&](const QString& fontArrayName) {\n        auto fontName = fontArrayName.isEmpty() ? ui_->fontArrayNameEdit->placeholderText() : std::move(fontArrayName);\n        debounceFontNameChanged(fontName);\n    });\n\n    connect(ui_->actionCheck_for_Updates, &QAction::triggered, [&] {\n        updateHelper_->checkForUpdates(true);\n    });\n}\n\nvoid MainWindow::connectViewModelOutputs()\n{\n    connect(viewModel_.get(), &MainWindowModel::documentTitleChanged, [&](const QString& title) {\n        setWindowTitle(QString(\"FontEdit (%1)\").arg(title));\n    });\n    connect(viewModel_.get(), &MainWindowModel::uiStateChanged, this, &MainWindow::updateUI);\n    connect(viewModel_.get(), &MainWindowModel::faceLoaded, [&](f2b::font::face& face) {\n        undoStack_->clear();\n        displayFace(face);\n    });\n    connect(viewModel_.get(), &MainWindowModel::documentError, this, &MainWindow::displayError);\n    connect(viewModel_.get(), &MainWindowModel::activeGlyphChanged, [&](std::optional<f2b::font::glyph> glyph) {\n        if (glyph.has_value()) {\n            displayGlyph(glyph.value());\n        } else if (auto g = glyphWidget_.get()) {\n            ui_->glyphGraphicsView->scene()->removeItem(g);\n            glyphWidget_.release();\n        }\n    });\n    connect(viewModel_.get(), &MainWindowModel::sourceCodeUpdating, [&]() {\n//        ui_->stackedWidget->setCurrentWidget(ui_->spinnerContainer);\n    });\n    connect(viewModel_.get(), &MainWindowModel::sourceCodeChanged, [&]() {\n        if (ui_->tabWidget->currentIndex() == codeTabIndex) {\n            displaySourceCode();\n        }\n    });\n    connect(viewModel_.get(), &MainWindowModel::documentClosed, this, &MainWindow::closeCurrentDocument);\n}\n\nvoid MainWindow::initUI()\n{\n    // hide not implemented UI\n    ui_->actionCopy_Glyph->setVisible(false);\n    ui_->actionPaste_Glyph->setVisible(false);\n    ui_->copyButton->setVisible(false);\n    ui_->pasteButton->setVisible(false);\n    ui_->printButton->setVisible(false);\n    ui_->actionPrint->setVisible(false);\n    ui_->actionNew->setVisible(false);\n\n    faceScene_->setBackgroundBrush(QBrush(Qt::lightGray));\n    ui_->faceGraphicsView->setScene(faceScene_.get());\n\n    ui_->actionShow_non_exported_Glyphs->setChecked(viewModel_->shouldShowNonExportedGlyphs());\n    ui_->showNonExportedGlyphsCheckBox->setCheckState(viewModel_->shouldShowNonExportedGlyphs());\n\n    ui_->showNonExportedGlyphsCheckBox->setVisible(false);\n    ui_->faceInfoSeparator->setVisible(false);\n    ui_->faceInfoLabel->setVisible(false);\n\n    auto scrollBarWidth = ui_->faceGraphicsView->verticalScrollBar()->sizeHint().width();\n    auto faceViewWidth = static_cast<int>(FaceWidget::cell_width) * 3 + scrollBarWidth;\n    ui_->faceGraphicsView->setMinimumSize({ faceViewWidth,\n                                            ui_->faceGraphicsView->minimumSize().height() });\n\n    ui_->exportAllButton->setChecked(viewModel_->exportAllEnabled());\n    ui_->exportSubsetButton->setChecked(!viewModel_->exportAllEnabled());\n    ui_->invertBitsCheckBox->setCheckState(viewModel_->invertBits());\n    ui_->bitNumberingCheckBox->setCheckState(viewModel_->msbEnabled());\n    ui_->lineSpacingCheckBox->setCheckState(viewModel_->includeLineSpacing());\n\n    for (const auto& [identifier, name] : viewModel_->outputFormats().toStdMap()) {\n        ui_->formatComboBox->addItem(name, identifier);\n    }\n    for (const auto& [indent, name] : viewModel_->indentationStyles()) {\n        ui_->indentationComboBox->addItem(name);\n    }\n\n    ui_->formatComboBox->setCurrentText(viewModel_->outputFormat());\n    ui_->indentationComboBox->setCurrentText(viewModel_->indentationStyleCaption());\n\n    QFont f(consoleFontName, 12);\n    f.setStyleHint(QFont::TypeWriter);\n    ui_->sourceCodeTextBrowser->setFont(f);\n}\n\nvoid MainWindow::showUpdateDialog(std::optional<UpdateHelper::Update> update)\n{\n    QMessageBox messageBox;\n    messageBox.setCheckBox(new QCheckBox(tr(\"Check for updates at start-up\")));\n    messageBox.checkBox()->setChecked(updateHelper_->shouldCheckAtStartup());\n\n    connect(messageBox.checkBox(), &QCheckBox::toggled, [&] (bool isChecked) {\n        updateHelper_->setShouldCheckAtStartup(isChecked);\n    });\n\n    if (update.has_value()) {\n        auto updateInfo = update.value();\n        messageBox.setText(tr(\"Update Available\"));\n        messageBox.setInformativeText(tr(\"FontEdit %1 is available (you have %2).\\nGet the new version from GitHub Releases Page.\")\n                                      .arg(updateInfo.latestVersion, updateInfo.currentVersion));\n        messageBox.setDetailedText(updateInfo.releaseNotes);\n\n        auto visitPageButton = messageBox.addButton(tr(\"Go to Releases Page\"), QMessageBox::YesRole);\n        messageBox.addButton(tr(\"Dismiss\"), QMessageBox::NoRole);\n\n        messageBox.setDefaultButton(visitPageButton);\n        messageBox.exec();\n\n        if (messageBox.clickedButton() == visitPageButton) {\n            QDesktopServices::openUrl(updateInfo.webpageURL);\n        }\n    } else {\n        messageBox.setText(tr(\"No Update Available\"));\n        messageBox.setInformativeText(tr(\"You're using the latest version of FontEdit\"));\n\n        messageBox.exec();\n    }\n\n}\n\nvoid MainWindow::closeCurrentDocument()\n{\n    ui_->showNonExportedGlyphsCheckBox->setVisible(false);\n    ui_->faceInfoSeparator->setVisible(false);\n    ui_->faceInfoLabel->setVisible(false);\n\n    if (faceWidget_ != nullptr) {\n        faceScene_->removeItem(faceWidget_);\n        delete faceWidget_;\n        faceWidget_ = nullptr;\n    }\n\n    if (auto g = glyphWidget_.get()) {\n        ui_->glyphGraphicsView->scene()->removeItem(g);\n        glyphWidget_.release();\n    }\n\n    undoStack_->clear();\n    updateResetActions();\n}\n\nvoid MainWindow::setupActions()\n{\n    auto undo = undoStack_->createUndoAction(this);\n    undo->setIcon(QIcon {\":/toolbar/assets/undo.svg\"});\n    undo->setShortcut(QKeySequence::Undo);\n\n    auto redo = undoStack_->createRedoAction(this);\n    redo->setIcon(QIcon {\":/toolbar/assets/redo.svg\"});\n    redo->setShortcut(QKeySequence::Redo);\n\n    ui_->openButton->setDefaultAction(ui_->actionOpen);\n    ui_->importFontButton->setDefaultAction(ui_->actionImport_Font);\n    ui_->addGlyphButton->setDefaultAction(ui_->actionAdd_Glyph);\n    ui_->deleteGlyphButton->setDefaultAction(ui_->actionDelete_Glyph);\n    ui_->saveButton->setDefaultAction(ui_->actionSave);\n    ui_->copyButton->setDefaultAction(ui_->actionCopy_Glyph);\n    ui_->pasteButton->setDefaultAction(ui_->actionPaste_Glyph);\n    ui_->undoButton->setDefaultAction(undo);\n    ui_->redoButton->setDefaultAction(redo);\n    ui_->resetGlyphButton->setDefaultAction(ui_->actionReset_Glyph);\n    ui_->resetFontButton->setDefaultAction(ui_->actionReset_Font);\n    ui_->actionReset_Glyph->setEnabled(false);\n    ui_->actionReset_Font->setEnabled(false);\n\n    ui_->menuEdit->insertAction(ui_->actionCopy_Glyph, undo);\n    ui_->menuEdit->insertAction(ui_->actionCopy_Glyph, redo);\n}\n\nvoid MainWindow::updateUI(UIState uiState)\n{\n    ui_->tabWidget->setTabEnabled(1, uiState.actions[UIState::InterfaceAction::ActionTabCode]);\n    ui_->actionAdd_Glyph->setEnabled(uiState.actions[UIState::InterfaceAction::ActionAddGlyph]);\n    ui_->actionDelete_Glyph->setEnabled(uiState.actions[UIState::InterfaceAction::ActionDeleteGlyph]);\n    ui_->actionSave->setEnabled(uiState.actions[UIState::InterfaceAction::ActionSave]);\n    ui_->actionSave_As->setEnabled(uiState.actions[UIState::InterfaceAction::ActionSave]);\n    ui_->actionClose->setEnabled(uiState.actions[UIState::InterfaceAction::ActionClose]);\n    ui_->actionCopy_Glyph->setEnabled(uiState.actions[UIState::InterfaceAction::ActionCopy]);\n    ui_->actionPaste_Glyph->setEnabled(uiState.actions[UIState::InterfaceAction::ActionPaste]);\n    ui_->actionExport->setEnabled(uiState.actions[UIState::InterfaceAction::ActionExport]);\n    ui_->actionPrint->setEnabled(uiState.actions[UIState::InterfaceAction::ActionPrint]);\n\n    switch (uiState.statusBarMessage) {\n    case UIState::MessageIdle:\n        statusLabel_->setText(tr(\"Start by importing a system font or opening an existing document\"));\n        break;\n    case UIState::MessageLoadedFace:\n        statusLabel_->setText(tr(\"Select a glyph on the right to edit it\"));\n        break;\n    case UIState::MessageLoadedGlyph:\n        statusLabel_->setText(tr(\"Click and drag to paint, hold Alt or Ctrl to erase.\"));\n        break;\n    }\n\n    if (ui_->tabWidget->currentIndex() == codeTabIndex) {\n        statusLabel_->setVisible(false);\n    } else {\n        statusLabel_->setVisible(true);\n    }\n    ui_->statusBar->clearMessage();\n}\n\nQString MainWindow::defaultDialogDirectory() const\n{\n    QString directoryPath = viewModel_->lastVisitedDirectory();\n    if (directoryPath.isNull()) {\n        directoryPath = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).last();\n    }\n    return directoryPath;\n}\n\nvoid MainWindow::showAboutDialog()\n{\n    auto about = new AboutDialog(this);\n    about->show();\n}\n\nvoid MainWindow::showFontDialog()\n{\n    switch (promptToSaveDirtyDocument()) {\n    case Save:\n        save();\n    case DontSave:\n        break;\n    case Cancel:\n        return;\n    }\n\n    bool ok;\n    QFont f(consoleFontName, 24);\n    f.setStyleHint(QFont::TypeWriter);\n    f = QFontDialog::getFont(&ok, f, this, tr(\"Select Font\"), QFontDialog::MonospacedFonts | QFontDialog::DontUseNativeDialog);\n\n    if (ok) {\n        qDebug() << \"selected font:\" << f;\n        viewModel_->importFont(f);\n    }\n}\n\nvoid MainWindow::showOpenDocumentDialog()\n{\n    switch (promptToSaveDirtyDocument()) {\n    case Save:\n        save();\n    case DontSave:\n        break;\n    case Cancel:\n        return;\n    }\n\n    QString fileName = QFileDialog::getOpenFileName(this, tr(\"Open Document\"), std::move(defaultDialogDirectory()), tr(fileFilter));\n\n    if (!fileName.isNull())\n        viewModel_->openDocument(fileName);\n}\n\nvoid MainWindow::showCloseDocumentDialogIfNeeded()\n{\n    switch (promptToSaveDirtyDocument()) {\n    case Save:\n        save();\n    case DontSave:\n        break;\n    case Cancel:\n        return;\n    }\n\n    viewModel_->closeCurrentDocument();\n}\n\nvoid MainWindow::showAddGlyphDialog()\n{\n    auto addGlyph = new AddGlyphDialog(*viewModel_->faceModel(), this);\n    addGlyph->show();\n\n    connect(addGlyph, &AddGlyphDialog::glyphSelected, [&](const std::optional<f2b::font::glyph>& glyph) {\n        if (glyph.has_value()) {\n\n            auto numberOfGlyphs = viewModel_->faceModel()->face().num_glyphs();\n            auto activeGlyphIndex = viewModel_->faceModel()->activeGlyphIndex();\n\n            pushUndoCommand(new Command(tr(\"Add Glyph\"), [&, numberOfGlyphs, activeGlyphIndex] {\n                viewModel_->deleteGlyph(numberOfGlyphs);\n                viewModel_->setActiveGlyphIndex(activeGlyphIndex);\n                displayFace(viewModel_->faceModel()->face());\n            }, [&, glyph] {\n                viewModel_->appendGlyph(glyph.value());\n                viewModel_->setActiveGlyphIndex(viewModel_->faceModel()->face().num_glyphs()-1);\n                displayFace(viewModel_->faceModel()->face());\n            }));\n        }\n    });\n}\n\nvoid MainWindow::showDeleteGlyphDialog()\n{\n    auto currentIndex = viewModel_->faceModel()->activeGlyphIndex();\n    auto isLastGlyph = currentIndex.value() == viewModel_->faceModel()->face().num_glyphs() - 1;\n    if (!isLastGlyph) {\n        QMessageBox::information(this, tr(\"Delete Glyph\"),\n                                 tr(\"You can only delete Glyphs at the end of the list. \"\n                                    \"This Glyph will be cleared instead of deleted \"\n                                    \"(to ensure that other Glyphs' indexes are unchanged).\"),\n                                 QMessageBox::StandardButton::Ok);\n    }\n\n    auto glyph = viewModel_->faceModel()->activeGlyph();\n    if (glyph.has_value()) {\n\n        auto commandName = isLastGlyph ? tr(\"Delete Glyph\") : tr(\"Clear Glyph\");\n\n        pushUndoCommand(new Command(commandName, [&, currentIndex, isLastGlyph, glyph] {\n            if (isLastGlyph) {\n                viewModel_->appendGlyph(glyph.value());\n                viewModel_->setActiveGlyphIndex(viewModel_->faceModel()->face().num_glyphs()-1);\n                displayFace(viewModel_->faceModel()->face());\n            } else {\n                viewModel_->modifyGlyph(currentIndex.value(), glyph.value());\n                faceWidget_->updateGlyphInfo(currentIndex.value(), viewModel_->faceModel()->activeGlyph().value());\n                displayGlyph(viewModel_->faceModel()->activeGlyph().value());\n            }\n        }, [&, currentIndex, isLastGlyph] {\n            viewModel_->deleteGlyph(currentIndex.value());\n            if (isLastGlyph) {\n                displayFace(viewModel_->faceModel()->face());\n            } else {\n                faceWidget_->updateGlyphInfo(currentIndex.value(), viewModel_->faceModel()->activeGlyph().value());\n                displayGlyph(viewModel_->faceModel()->activeGlyph().value());\n            }\n        }));\n\n    }\n}\n\nvoid MainWindow::save()\n{\n    auto currentPath = viewModel_->currentDocumentPath();\n    if (currentPath.has_value()) {\n        viewModel_->saveDocument(currentPath.value());\n    } else {\n        saveAs();\n    }\n}\n\nvoid MainWindow::saveAs()\n{\n    QString fileName = QFileDialog::getSaveFileName(this, tr(\"Save Document\"), std::move(defaultDialogDirectory()), tr(fileFilter));\n\n    if (!fileName.isNull())\n        viewModel_->saveDocument(fileName);\n}\n\nvoid MainWindow::displayError(const QString &error)\n{\n    QMessageBox::critical(this, tr(\"Error\"), error, QMessageBox::StandardButton::Ok);\n}\n\nvoid MainWindow::closeEvent(QCloseEvent *event)\n{\n    switch (promptToSaveDirtyDocument()) {\n    case Save:\n        save();\n    case DontSave:\n        event->accept();\n        break;\n    case Cancel:\n        event->ignore();\n    }\n}\n\nMainWindow::SavePromptButton MainWindow::promptToSaveDirtyDocument()\n{\n    if (viewModel_->faceModel() == nullptr || !viewModel_->faceModel()->isModifiedSinceSave()) {\n        return DontSave; // ignore this dialog and move on\n    }\n\n    QStringList buttons { tr(\"Save\"), tr(\"Don't Save\"), tr(\"Cancel\") };\n    auto ret = QMessageBox::information(this,\n                                        \"\",\n                                        tr(\"Do you want to save the changes you made? Your changes will be lost if you don't save them.\"),\n                                        buttons[0], buttons[1], buttons[2], 0, 2);\n    return static_cast<MainWindow::SavePromptButton>(ret);\n}\n\nvoid MainWindow::displayFace(f2b::font::face& face)\n{\n    if (faceWidget_ == nullptr) {\n        faceWidget_ = new FaceWidget();\n        faceWidget_->setShowsNonExportedItems(viewModel_->shouldShowNonExportedGlyphs());\n        ui_->faceGraphicsView->scene()->addItem(faceWidget_);\n\n        connect(faceWidget_, &FaceWidget::currentGlyphIndexChanged,\n                this, &MainWindow::switchActiveGlyph);\n        connect(faceWidget_, &FaceWidget::glyphExportedStateChanged,\n                this, &MainWindow::setGlyphExported);\n        connect(ui_->actionShow_non_exported_Glyphs, &QAction::toggled,\n                [&](bool checked) {\n            viewModel_->setShouldShowNonExportedGlyphs(checked);\n            faceWidget_->setShowsNonExportedItems(checked);\n            QApplication::processEvents();\n            ui_->faceGraphicsView->setSceneRect(faceWidget_->rect());\n        });\n    }\n\n    auto margins = viewModel_->faceModel()->originalFaceMargins();\n    faceWidget_->load(face, margins);\n\n    ui_->showNonExportedGlyphsCheckBox->setVisible(true);\n    ui_->faceInfoSeparator->setVisible(true);\n\n    auto faceInfo = viewModel_->faceModel()->faceInfo();\n    updateFaceInfoLabel(faceInfo);\n    updateDefaultFontName(faceInfo);\n    ui_->faceInfoLabel->setVisible(true);\n\n    auto glyph = viewModel_->faceModel()->activeGlyph();\n    if (glyph.has_value()) {\n        displayGlyph(glyph.value());\n        faceWidget_->setCurrentGlyphIndex(viewModel_->faceModel()->activeGlyphIndex());\n        viewModel_->setActiveGlyphIndex(viewModel_->faceModel()->activeGlyphIndex());\n    } else if (auto g = glyphWidget_.get()) {\n        ui_->glyphGraphicsView->scene()->removeItem(g);\n        glyphWidget_.release();\n    }\n    updateResetActions();\n}\n\nvoid MainWindow::setGlyphExported(std::size_t index, bool isExported)\n{\n    pushUndoCommand(new Command(tr(\"Toggle Glyph Exported\"), [&, index, isExported] {\n        viewModel_->setGlyphExported(index, !isExported);\n        auto faceModel = viewModel_->faceModel();\n        updateFaceInfoLabel(faceModel->faceInfo());\n        if (!faceWidget_->showsNonExportedItems()) {\n            auto margins = faceModel->originalFaceMargins();\n            faceWidget_->load(faceModel->face(), margins);\n            faceWidget_->setCurrentGlyphIndex(index);\n            glyphWidget_->load(faceModel->face().glyph_at(index), margins);\n        } else {\n            faceWidget_->updateGlyphInfo(index, {}, !isExported);\n        }\n\n    }, [&, index, isExported] {\n        auto faceModel = viewModel_->faceModel();\n        auto shouldUpdateCurrentIndex = !isExported && !faceWidget_->showsNonExportedItems();\n\n        std::optional<std::size_t> nextIndex {};\n        if (shouldUpdateCurrentIndex) {\n            // Find index of the next exported item\n            auto i = std::next(faceModel->face().exported_glyph_ids().find(index));\n            if (i != faceModel->face().exported_glyph_ids().end()) {\n                nextIndex = *i;\n            }\n        }\n\n        viewModel_->setGlyphExported(index, isExported);\n        updateFaceInfoLabel(faceModel->faceInfo());\n\n        if (shouldUpdateCurrentIndex) {\n            auto margins = faceModel->originalFaceMargins();\n            faceWidget_->load(faceModel->face(), margins);\n            faceWidget_->setCurrentGlyphIndex(nextIndex);\n            if (nextIndex.has_value()) {\n                glyphWidget_->load(faceModel->face().glyph_at(nextIndex.value()), margins);\n            }\n        } else {\n            faceWidget_->updateGlyphInfo(index, {}, isExported);\n        }\n    }));\n}\n\nvoid MainWindow::updateFaceInfoLabel(const FaceInfo &faceInfo)\n{\n    QStringList lines;\n    lines << faceInfo.fontName;\n    lines << tr(\"Size (full): %1x%2px\").arg(faceInfo.size.width).arg(faceInfo.size.height);\n    lines << tr(\"Size (adjusted): %1x%2px\").arg(faceInfo.sizeWithoutMargins.width).arg(faceInfo.sizeWithoutMargins.height);\n    lines << QString(\"%1, %2\").arg(tr(\"%n Glyph(s)\", \"\", faceInfo.numberOfGlyphs),\n                                   tr(\"%n to export\", \"\", faceInfo.numberOfExportedGlyphs));\n    ui_->faceInfoLabel->setText(lines.join(\"\\n\"));\n}\n\nvoid MainWindow::updateDefaultFontName(const FaceInfo &faceInfo)\n{\n    auto fontName = faceInfo.fontName.toLower();\n    fontName.remove(',');\n    fontName.replace(' ', '_');\n    ui_->fontArrayNameEdit->setText(fontName);\n}\n\nvoid MainWindow::displayGlyph(const f2b::font::glyph& glyph)\n{\n    auto margins = viewModel_->faceModel()->originalFaceMargins();\n    if (!glyphWidget_.get()) {\n        glyphWidget_ = std::make_unique<GlyphWidget>(glyph, margins);\n        ui_->glyphGraphicsView->scene()->addItem(glyphWidget_.get());\n\n        connect(glyphWidget_.get(), &GlyphWidget::pixelsChanged,\n                this, &MainWindow::editGlyph);\n    } else {\n        glyphWidget_->load(glyph, margins);\n    }\n    updateResetActions();\n    ui_->glyphGraphicsView->fitInView(glyphWidget_->boundingRect(), Qt::KeepAspectRatio);\n}\n\nvoid MainWindow::editGlyph(const BatchPixelChange& change)\n{\n    auto currentIndex = viewModel_->faceModel()->activeGlyphIndex();\n    if (currentIndex.has_value()) {\n\n        auto applyChange = [&, currentIndex, change](BatchPixelChange::ChangeType type) -> std::function<void()> {\n            return [&, currentIndex, change, type] {\n                viewModel_->modifyGlyph(currentIndex.value(), change, type);\n                updateResetActions();\n                glyphWidget_->applyChange(change, type);\n                faceWidget_->updateGlyphInfo(currentIndex.value(), viewModel_->faceModel()->activeGlyph().value());\n                viewModel_->updateDocumentTitle();\n            };\n        };\n\n        pushUndoCommand(new Command(tr(\"Edit Glyph\"),\n                                    applyChange(BatchPixelChange::ChangeType::Reverse),\n                                    applyChange(BatchPixelChange::ChangeType::Normal)));\n    }\n}\n\nvoid MainWindow::switchActiveGlyph(std::optional<std::size_t> newIndex)\n{\n    auto currentIndex = viewModel_->faceModel()->activeGlyphIndex();\n    if (currentIndex == newIndex) {\n        return;\n    }\n\n    if (currentIndex.has_value() && newIndex.has_value()) {\n\n        if (!pendingSwitchGlyphCommand_) {\n            pendingSwitchGlyphCommand_ = std::make_unique<SwitchActiveGlyphCommand>(faceWidget_, viewModel_.get(),\n                                                                                    currentIndex.value(), newIndex.value());\n        } else {\n            pendingSwitchGlyphCommand_->setToIndex(newIndex.value());\n        }\n        pendingSwitchGlyphCommand_->redo();\n    } else {\n        faceWidget_->setCurrentGlyphIndex(newIndex);\n        viewModel_->setActiveGlyphIndex(newIndex);\n    }\n}\n\nvoid MainWindow::resetCurrentGlyph()\n{\n    f2b::font::glyph currentGlyphState { viewModel_->faceModel()->activeGlyph().value() };\n    auto glyphIndex = viewModel_->faceModel()->activeGlyphIndex().value();\n\n    pushUndoCommand(new Command(tr(\"Reset Glyph\"), [&, currentGlyphState, glyphIndex] {\n        viewModel_->modifyGlyph(glyphIndex, currentGlyphState);\n        viewModel_->updateDocumentTitle();\n        displayGlyph(viewModel_->faceModel()->activeGlyph().value());\n        faceWidget_->updateGlyphInfo(glyphIndex, viewModel_->faceModel()->activeGlyph().value());\n    }, [&, glyphIndex] {\n        viewModel_->resetGlyph(glyphIndex);\n        viewModel_->updateDocumentTitle();\n        displayGlyph(viewModel_->faceModel()->activeGlyph().value());\n        faceWidget_->updateGlyphInfo(glyphIndex, viewModel_->faceModel()->activeGlyph().value());\n    }));\n}\n\nvoid MainWindow::resetFont()\n{\n    auto message = tr(\"Are you sure you want to reset all changes to the font? This operation cannot be undone.\");\n    auto result = QMessageBox::warning(this, tr(\"Reset Font\"), message, QMessageBox::Reset, QMessageBox::Cancel);\n    if (result == QMessageBox::Reset) {\n        viewModel_->faceModel()->reset();\n        viewModel_->updateDocumentTitle();\n        undoStack_->clear();\n        updateResetActions();\n        displayFace(viewModel_->faceModel()->face());\n    }\n}\n\nvoid MainWindow::updateResetActions()\n{\n    if (viewModel_->faceModel() == nullptr) {\n        ui_->actionReset_Glyph->setEnabled(false);\n        ui_->actionReset_Font->setEnabled(false);\n    } else {\n        auto currentIndex = viewModel_->faceModel()->activeGlyphIndex();\n        if (currentIndex.has_value()) {\n            ui_->actionReset_Glyph->setEnabled(viewModel_->faceModel()->isGlyphModified(currentIndex.value()));\n        } else {\n            ui_->actionReset_Glyph->setEnabled(false);\n        }\n        ui_->actionReset_Font->setEnabled(viewModel_->faceModel()->isModified());\n    }\n}\n\nvoid MainWindow::debounceFontNameChanged(const QString &fontName)\n{\n    if (fontNameDebounceTimer_ == nullptr) {\n        fontNameDebounceTimer_ = std::make_unique<QTimer>();\n        fontNameDebounceTimer_->setInterval(300);\n        fontNameDebounceTimer_->setSingleShot(true);\n    } else {\n        fontNameDebounceTimer_->stop();\n        fontNameDebounceTimer_->disconnect();\n    }\n\n    connect(fontNameDebounceTimer_.get(), &QTimer::timeout, [&, fontName] {\n        viewModel_->setFontArrayName(fontName);\n    });\n\n    fontNameDebounceTimer_->start();\n}\n\nvoid MainWindow::displaySourceCode()\n{\n    ui_->stackedWidget->setCurrentWidget(ui_->sourceCodeContainer);\n    QElapsedTimer timer;\n    timer.start();\n    ui_->sourceCodeTextBrowser->setPlainText(viewModel_->sourceCode());\n    qDebug() << \"Displaying finished in\" << timer.elapsed() << \"ms\";\n}\n\nvoid MainWindow::exportSourceCode()\n{\n    ui_->statusBar->clearMessage();\n\n    QString directoryPath = viewModel_->lastSourceCodeDirectory();\n    if (directoryPath.isNull()) {\n        directoryPath = defaultDialogDirectory();\n    }\n\n    auto dialog = std::make_shared<QFileDialog>(this, tr(\"Save Source Code\"), directoryPath);\n    dialog->setAcceptMode(QFileDialog::AcceptSave);\n    dialog->setFileMode(QFileDialog::AnyFile);\n\n    connect(dialog.get(), &QFileDialog::finished, [=](int) {\n        dialog->setParent(nullptr);\n        auto files = dialog->selectedFiles();\n        if (!files.isEmpty()) {\n            auto filePath = files.first();\n            if (!filePath.isNull()) {\n                QFile output(filePath);\n                if (output.open(QFile::WriteOnly | QFile::Truncate)) {\n                    output.write(ui_->sourceCodeTextBrowser->document()->toPlainText().toUtf8());\n                    output.close();\n                    viewModel_->setLastSourceCodeDirectory(filePath);\n                    ui_->statusBar->showMessage(tr(\"Source code successfully exported.\"), 5000);\n                } else {\n                    displayError(tr(\"Unable to write to file: \") + filePath);\n                }\n            }\n        }\n    });\n\n    dialog->open();\n}\n\nvoid MainWindow::pushUndoCommand(QUndoCommand *command)\n{\n    bool shouldPushSwitchGlyphCommand = pendingSwitchGlyphCommand_ != nullptr;\n\n    if (shouldPushSwitchGlyphCommand) {\n        undoStack_->beginMacro(command->text());\n        undoStack_->push(pendingSwitchGlyphCommand_.get());\n        pendingSwitchGlyphCommand_.release();\n    }\n\n    undoStack_->push(command);\n\n    if (shouldPushSwitchGlyphCommand) {\n        undoStack_->endMacro();\n    }\n}\n"
  },
  {
    "path": "app/mainwindow.h",
    "content": "#ifndef MAINWINDOW_H\n#define MAINWINDOW_H\n\n#include <QMainWindow>\n#include <QGraphicsScene>\n#include <QUndoStack>\n#include <QTimer>\n\n#include \"mainwindowmodel.h\"\n#include \"updatehelper.h\"\n#include \"facewidget.h\"\n#include \"glyphwidget.h\"\n#include \"batchpixelchange.h\"\n#include \"command.h\"\n\n#include <memory>\n\nnamespace Ui {\nclass MainWindow;\n}\n\nclass QLabel;\n\nclass MainWindow : public QMainWindow\n{\n    Q_OBJECT\n\npublic:\n    explicit MainWindow(QWidget *parent = nullptr);\n    virtual ~MainWindow();\n\nprotected:\n    virtual void closeEvent(QCloseEvent *event) override;\n\nprivate slots:\n    void displayFace(f2b::font::face& face);\n\nprivate:\n    void connectUpdateHelper();\n    void connectUIInputs();\n    void connectViewModelOutputs();\n    void initUI();\n    void setupActions();\n\n    void showUpdateDialog(std::optional<UpdateHelper::Update> update);\n\n    void showAboutDialog();\n    void showFontDialog();\n    void showOpenDocumentDialog();\n    void showCloseDocumentDialogIfNeeded();\n    void showAddGlyphDialog();\n    void showDeleteGlyphDialog();\n    void save();\n    void saveAs();\n    void resetCurrentGlyph();\n    void resetFont();\n    void displayGlyph(const f2b::font::glyph& glyph);\n    void updateUI(UIState uiState);\n    void editGlyph(const BatchPixelChange& change);\n    void switchActiveGlyph(std::optional<std::size_t> newIndex);\n    void setGlyphExported(std::size_t index, bool isExported);\n    void updateResetActions();\n    void updateFaceInfoLabel(const FaceInfo& faceInfo);\n    void updateDefaultFontName(const FaceInfo& faceInfo);\n\n    void displaySourceCode();\n    void exportSourceCode();\n    void closeCurrentDocument();\n    void displayError(const QString& error);\n    void pushUndoCommand(QUndoCommand *command);\n\n    void debounceFontNameChanged(const QString& fontName);\n\n    QString defaultDialogDirectory() const;\n\n    enum SavePromptButton {\n        Save,\n        DontSave,\n        Cancel\n    };\n\n    SavePromptButton promptToSaveDirtyDocument();\n\n    Ui::MainWindow *ui_;\n\n    std::unique_ptr<GlyphWidget> glyphWidget_ {};\n    FaceWidget *faceWidget_ { nullptr };\n    QLabel *statusLabel_;\n    std::unique_ptr<UpdateHelper> updateHelper_ { std::make_unique<UpdateHelper>() };\n    std::unique_ptr<MainWindowModel> viewModel_ { std::make_unique<MainWindowModel>() };\n    std::unique_ptr<QGraphicsScene> faceScene_ { std::make_unique<QGraphicsScene>() };\n    std::unique_ptr<QUndoStack> undoStack_ { std::make_unique<QUndoStack>() };\n    std::unique_ptr<QTimer> fontNameDebounceTimer_ {};\n\n    std::unique_ptr<SwitchActiveGlyphCommand> pendingSwitchGlyphCommand_ {};\n};\n\n#endif // MAINWINDOW_H\n"
  },
  {
    "path": "app/mainwindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>MainWindow</class>\n <widget class=\"QMainWindow\" name=\"MainWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>724</width>\n    <height>619</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>FontEdit</string>\n  </property>\n  <property name=\"windowIcon\">\n   <iconset resource=\"assets.qrc\">\n    <normaloff>:/icon/assets/icon/fontedit256.png</normaloff>:/icon/assets/icon/fontedit256.png</iconset>\n  </property>\n  <widget class=\"QWidget\" name=\"centralwidget\">\n   <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n    <item>\n     <widget class=\"QTabWidget\" name=\"tabWidget\">\n      <property name=\"tabShape\">\n       <enum>QTabWidget::Triangular</enum>\n      </property>\n      <property name=\"currentIndex\">\n       <number>0</number>\n      </property>\n      <widget class=\"QWidget\" name=\"tabEdit\">\n       <attribute name=\"title\">\n        <string>Edit Font</string>\n       </attribute>\n       <layout class=\"QGridLayout\" name=\"gridLayout\">\n        <item row=\"0\" column=\"0\">\n         <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n          <property name=\"spacing\">\n           <number>0</number>\n          </property>\n          <item>\n           <widget class=\"QToolButton\" name=\"importFontButton\">\n            <property name=\"iconSize\">\n             <size>\n              <width>24</width>\n              <height>24</height>\n             </size>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QToolButton\" name=\"openButton\">\n            <property name=\"text\">\n             <string/>\n            </property>\n            <property name=\"iconSize\">\n             <size>\n              <width>24</width>\n              <height>24</height>\n             </size>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QToolButton\" name=\"saveButton\">\n            <property name=\"iconSize\">\n             <size>\n              <width>24</width>\n              <height>24</height>\n             </size>\n            </property>\n            <property name=\"shortcut\">\n             <string>Ctrl+R</string>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <spacer name=\"horizontalSpacer_4\">\n            <property name=\"orientation\">\n             <enum>Qt::Horizontal</enum>\n            </property>\n            <property name=\"sizeType\">\n             <enum>QSizePolicy::Fixed</enum>\n            </property>\n            <property name=\"sizeHint\" stdset=\"0\">\n             <size>\n              <width>8</width>\n              <height>20</height>\n             </size>\n            </property>\n           </spacer>\n          </item>\n          <item>\n           <widget class=\"QToolButton\" name=\"addGlyphButton\">\n            <property name=\"iconSize\">\n             <size>\n              <width>24</width>\n              <height>24</height>\n             </size>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QToolButton\" name=\"deleteGlyphButton\">\n            <property name=\"text\">\n             <string/>\n            </property>\n            <property name=\"iconSize\">\n             <size>\n              <width>24</width>\n              <height>24</height>\n             </size>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QToolButton\" name=\"copyButton\">\n            <property name=\"iconSize\">\n             <size>\n              <width>24</width>\n              <height>24</height>\n             </size>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QToolButton\" name=\"pasteButton\">\n            <property name=\"iconSize\">\n             <size>\n              <width>24</width>\n              <height>24</height>\n             </size>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QToolButton\" name=\"undoButton\">\n            <property name=\"iconSize\">\n             <size>\n              <width>24</width>\n              <height>24</height>\n             </size>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QToolButton\" name=\"redoButton\">\n            <property name=\"iconSize\">\n             <size>\n              <width>24</width>\n              <height>24</height>\n             </size>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QToolButton\" name=\"resetGlyphButton\">\n            <property name=\"iconSize\">\n             <size>\n              <width>24</width>\n              <height>24</height>\n             </size>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <spacer name=\"horizontalSpacer_5\">\n            <property name=\"orientation\">\n             <enum>Qt::Horizontal</enum>\n            </property>\n            <property name=\"sizeType\">\n             <enum>QSizePolicy::Fixed</enum>\n            </property>\n            <property name=\"sizeHint\" stdset=\"0\">\n             <size>\n              <width>8</width>\n              <height>20</height>\n             </size>\n            </property>\n           </spacer>\n          </item>\n          <item>\n           <widget class=\"QToolButton\" name=\"resetFontButton\">\n            <property name=\"iconSize\">\n             <size>\n              <width>24</width>\n              <height>24</height>\n             </size>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <spacer name=\"horizontalSpacer\">\n            <property name=\"orientation\">\n             <enum>Qt::Horizontal</enum>\n            </property>\n            <property name=\"sizeHint\" stdset=\"0\">\n             <size>\n              <width>40</width>\n              <height>20</height>\n             </size>\n            </property>\n           </spacer>\n          </item>\n         </layout>\n        </item>\n        <item row=\"1\" column=\"0\">\n         <widget class=\"GlyphGraphicsView\" name=\"glyphGraphicsView\">\n          <property name=\"alignment\">\n           <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>\n          </property>\n         </widget>\n        </item>\n        <item row=\"1\" column=\"1\">\n         <layout class=\"QVBoxLayout\" name=\"verticalLayout_8\">\n          <item>\n           <widget class=\"QGraphicsView\" name=\"faceGraphicsView\">\n            <property name=\"minimumSize\">\n             <size>\n              <width>240</width>\n              <height>0</height>\n             </size>\n            </property>\n            <property name=\"maximumSize\">\n             <size>\n              <width>240</width>\n              <height>16777215</height>\n             </size>\n            </property>\n            <property name=\"horizontalScrollBarPolicy\">\n             <enum>Qt::ScrollBarAlwaysOff</enum>\n            </property>\n            <property name=\"alignment\">\n             <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QCheckBox\" name=\"showNonExportedGlyphsCheckBox\">\n            <property name=\"statusTip\">\n             <string>Toggle exported state by pressing Space on a selected glyph</string>\n            </property>\n            <property name=\"text\">\n             <string>Show non-exported Glyphs</string>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"Line\" name=\"faceInfoSeparator\">\n            <property name=\"orientation\">\n             <enum>Qt::Horizontal</enum>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QLabel\" name=\"faceInfoLabel\">\n            <property name=\"enabled\">\n             <bool>true</bool>\n            </property>\n            <property name=\"maximumSize\">\n             <size>\n              <width>240</width>\n              <height>16777215</height>\n             </size>\n            </property>\n            <property name=\"text\">\n             <string>TextLabel</string>\n            </property>\n            <property name=\"alignment\">\n             <set>Qt::AlignCenter</set>\n            </property>\n            <property name=\"wordWrap\">\n             <bool>true</bool>\n            </property>\n           </widget>\n          </item>\n         </layout>\n        </item>\n        <item row=\"0\" column=\"1\">\n         <spacer name=\"horizontalSpacer_3\">\n          <property name=\"orientation\">\n           <enum>Qt::Horizontal</enum>\n          </property>\n          <property name=\"sizeType\">\n           <enum>QSizePolicy::Ignored</enum>\n          </property>\n          <property name=\"sizeHint\" stdset=\"0\">\n           <size>\n            <width>237</width>\n            <height>20</height>\n           </size>\n          </property>\n         </spacer>\n        </item>\n       </layout>\n      </widget>\n      <widget class=\"QWidget\" name=\"tabCode\">\n       <attribute name=\"title\">\n        <string>Source Code</string>\n       </attribute>\n       <layout class=\"QHBoxLayout\" name=\"horizontalLayout_3\">\n        <item>\n         <widget class=\"QStackedWidget\" name=\"stackedWidget\">\n          <property name=\"currentIndex\">\n           <number>0</number>\n          </property>\n          <widget class=\"QWidget\" name=\"sourceCodeContainer\">\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\n            <property name=\"leftMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"topMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"rightMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"bottomMargin\">\n             <number>0</number>\n            </property>\n            <item>\n             <widget class=\"QTextBrowser\" name=\"sourceCodeTextBrowser\">\n              <property name=\"lineWrapMode\">\n               <enum>QTextEdit::NoWrap</enum>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n          <widget class=\"QWidget\" name=\"spinnerContainer\">\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_4\">\n            <item>\n             <widget class=\"QLabel\" name=\"label\">\n              <property name=\"text\">\n               <string>Updating source code...</string>\n              </property>\n              <property name=\"alignment\">\n               <set>Qt::AlignCenter</set>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </widget>\n        </item>\n        <item>\n         <layout class=\"QVBoxLayout\" name=\"verticalLayout_4\">\n          <item>\n           <widget class=\"QGroupBox\" name=\"groupBox_2\">\n            <property name=\"title\">\n             <string>Output Format</string>\n            </property>\n            <layout class=\"QVBoxLayout\" name=\"verticalLayout_3\">\n             <item>\n              <widget class=\"QComboBox\" name=\"formatComboBox\"/>\n             </item>\n             <item>\n              <widget class=\"QRadioButton\" name=\"exportSubsetButton\">\n               <property name=\"text\">\n                <string>Export Selected Glyphs</string>\n               </property>\n               <attribute name=\"buttonGroup\">\n                <string notr=\"true\">exportMethodButtonGroup</string>\n               </attribute>\n              </widget>\n             </item>\n             <item>\n              <widget class=\"QRadioButton\" name=\"exportAllButton\">\n               <property name=\"text\">\n                <string>Export All Glyphs</string>\n               </property>\n               <attribute name=\"buttonGroup\">\n                <string notr=\"true\">exportMethodButtonGroup</string>\n               </attribute>\n              </widget>\n             </item>\n            </layout>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QGroupBox\" name=\"groupBox\">\n            <property name=\"sizePolicy\">\n             <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n              <horstretch>0</horstretch>\n              <verstretch>0</verstretch>\n             </sizepolicy>\n            </property>\n            <property name=\"title\">\n             <string>Misc Options</string>\n            </property>\n            <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n             <item>\n              <widget class=\"QCheckBox\" name=\"invertBitsCheckBox\">\n               <property name=\"text\">\n                <string>Invert Bits</string>\n               </property>\n              </widget>\n             </item>\n             <item>\n              <widget class=\"QCheckBox\" name=\"bitNumberingCheckBox\">\n               <property name=\"text\">\n                <string>Reverse Bits (MSB)</string>\n               </property>\n              </widget>\n             </item>\n             <item>\n              <widget class=\"QCheckBox\" name=\"lineSpacingCheckBox\">\n               <property name=\"text\">\n                <string>Include Line Spacing</string>\n               </property>\n              </widget>\n             </item>\n            </layout>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QGroupBox\" name=\"groupBox_3\">\n            <property name=\"title\">\n             <string>Font Array Name</string>\n            </property>\n            <layout class=\"QHBoxLayout\" name=\"horizontalLayout_5\">\n             <item>\n              <widget class=\"QLineEdit\" name=\"fontArrayNameEdit\">\n               <property name=\"sizePolicy\">\n                <sizepolicy hsizetype=\"Ignored\" vsizetype=\"Fixed\">\n                 <horstretch>0</horstretch>\n                 <verstretch>0</verstretch>\n                </sizepolicy>\n               </property>\n               <property name=\"placeholderText\">\n                <string>font</string>\n               </property>\n              </widget>\n             </item>\n            </layout>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QGroupBox\" name=\"groupBox_4\">\n            <property name=\"title\">\n             <string>Indentation</string>\n            </property>\n            <layout class=\"QVBoxLayout\" name=\"verticalLayout_5\">\n             <item>\n              <widget class=\"QComboBox\" name=\"indentationComboBox\"/>\n             </item>\n            </layout>\n           </widget>\n          </item>\n          <item>\n           <spacer name=\"verticalSpacer\">\n            <property name=\"orientation\">\n             <enum>Qt::Vertical</enum>\n            </property>\n            <property name=\"sizeHint\" stdset=\"0\">\n             <size>\n              <width>20</width>\n              <height>40</height>\n             </size>\n            </property>\n           </spacer>\n          </item>\n          <item>\n           <widget class=\"QPushButton\" name=\"printButton\">\n            <property name=\"text\">\n             <string>Print</string>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QPushButton\" name=\"exportButton\">\n            <property name=\"text\">\n             <string>Export Source Code...</string>\n            </property>\n           </widget>\n          </item>\n         </layout>\n        </item>\n       </layout>\n      </widget>\n     </widget>\n    </item>\n   </layout>\n  </widget>\n  <widget class=\"QMenuBar\" name=\"menubar\">\n   <property name=\"geometry\">\n    <rect>\n     <x>0</x>\n     <y>0</y>\n     <width>724</width>\n     <height>22</height>\n    </rect>\n   </property>\n   <widget class=\"QMenu\" name=\"menuFile\">\n    <property name=\"title\">\n     <string>File</string>\n    </property>\n    <addaction name=\"actionImport_Font\"/>\n    <addaction name=\"actionOpen\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionSave\"/>\n    <addaction name=\"actionSave_As\"/>\n    <addaction name=\"actionExport\"/>\n    <addaction name=\"actionClose\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionPrint\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionQuit\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuEdit\">\n    <property name=\"title\">\n     <string>Edit</string>\n    </property>\n    <addaction name=\"actionAdd_Glyph\"/>\n    <addaction name=\"actionDelete_Glyph\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionCopy_Glyph\"/>\n    <addaction name=\"actionPaste_Glyph\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionReset_Glyph\"/>\n    <addaction name=\"actionReset_Font\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuHelp\">\n    <property name=\"title\">\n     <string>Help</string>\n    </property>\n    <addaction name=\"actionAbout\"/>\n    <addaction name=\"actionCheck_for_Updates\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuView\">\n    <property name=\"title\">\n     <string>View</string>\n    </property>\n    <addaction name=\"actionShow_non_exported_Glyphs\"/>\n   </widget>\n   <addaction name=\"menuFile\"/>\n   <addaction name=\"menuEdit\"/>\n   <addaction name=\"menuView\"/>\n   <addaction name=\"menuHelp\"/>\n  </widget>\n  <widget class=\"QStatusBar\" name=\"statusBar\"/>\n  <action name=\"actionNew\">\n   <property name=\"text\">\n    <string>New</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+N</string>\n   </property>\n  </action>\n  <action name=\"actionImport_Font\">\n   <property name=\"icon\">\n    <iconset resource=\"assets.qrc\">\n     <normaloff>:/toolbar/assets/font.svg</normaloff>:/toolbar/assets/font.svg</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Import Font</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+N</string>\n   </property>\n  </action>\n  <action name=\"actionReset_Glyph\">\n   <property name=\"icon\">\n    <iconset resource=\"assets.qrc\">\n     <normaloff>:/toolbar/assets/reset.svg</normaloff>:/toolbar/assets/reset.svg</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Reset Glyph</string>\n   </property>\n  </action>\n  <action name=\"actionExport\">\n   <property name=\"icon\">\n    <iconset resource=\"assets.qrc\">\n     <normaloff>:/toolbar/assets/code.svg</normaloff>:/toolbar/assets/code.svg</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Export...</string>\n   </property>\n  </action>\n  <action name=\"actionSave\">\n   <property name=\"icon\">\n    <iconset resource=\"assets.qrc\">\n     <normaloff>:/toolbar/assets/save.svg</normaloff>:/toolbar/assets/save.svg</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Save</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+S</string>\n   </property>\n  </action>\n  <action name=\"actionReset_Font\">\n   <property name=\"icon\">\n    <iconset resource=\"assets.qrc\">\n     <normaloff>:/toolbar/assets/clear.svg</normaloff>:/toolbar/assets/clear.svg</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Reset Font</string>\n   </property>\n  </action>\n  <action name=\"actionPrint\">\n   <property name=\"icon\">\n    <iconset resource=\"assets.qrc\">\n     <normaloff>:/toolbar/assets/print.svg</normaloff>:/toolbar/assets/print.svg</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Print...</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+P</string>\n   </property>\n  </action>\n  <action name=\"actionSave_As\">\n   <property name=\"text\">\n    <string>Save As...</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+Shift+S</string>\n   </property>\n  </action>\n  <action name=\"actionAdd_Glyph\">\n   <property name=\"icon\">\n    <iconset resource=\"assets.qrc\">\n     <normaloff>:/toolbar/assets/add-glyph.svg</normaloff>:/toolbar/assets/add-glyph.svg</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Add Glyph</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+Shift+N</string>\n   </property>\n  </action>\n  <action name=\"actionQuit\">\n   <property name=\"text\">\n    <string>Quit</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+Q</string>\n   </property>\n  </action>\n  <action name=\"actionCopy_Glyph\">\n   <property name=\"icon\">\n    <iconset resource=\"assets.qrc\">\n     <normaloff>:/toolbar/assets/copy.svg</normaloff>:/toolbar/assets/copy.svg</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Copy Glyph</string>\n   </property>\n  </action>\n  <action name=\"actionPaste_Glyph\">\n   <property name=\"icon\">\n    <iconset resource=\"assets.qrc\">\n     <normaloff>:/toolbar/assets/paste.svg</normaloff>:/toolbar/assets/paste.svg</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Paste Glyph</string>\n   </property>\n  </action>\n  <action name=\"actionOpen\">\n   <property name=\"icon\">\n    <iconset resource=\"assets.qrc\">\n     <normaloff>:/toolbar/assets/open.svg</normaloff>:/toolbar/assets/open.svg</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Open...</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+O</string>\n   </property>\n  </action>\n  <action name=\"actionOpen_Recent\">\n   <property name=\"text\">\n    <string>Recent files</string>\n   </property>\n  </action>\n  <action name=\"actionClose\">\n   <property name=\"text\">\n    <string>Close</string>\n   </property>\n  </action>\n  <action name=\"actionAbout\">\n   <property name=\"text\">\n    <string>About FontEdit</string>\n   </property>\n  </action>\n  <action name=\"actionDelete_Glyph\">\n   <property name=\"icon\">\n    <iconset resource=\"assets.qrc\">\n     <normaloff>:/toolbar/assets/delete-glyph.svg</normaloff>:/toolbar/assets/delete-glyph.svg</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Delete Glyph</string>\n   </property>\n  </action>\n  <action name=\"actionShow_non_exported_Glyphs\">\n   <property name=\"checkable\">\n    <bool>true</bool>\n   </property>\n   <property name=\"checked\">\n    <bool>true</bool>\n   </property>\n   <property name=\"text\">\n    <string>Show non-exported Glyphs</string>\n   </property>\n  </action>\n  <action name=\"actionCheck_for_Updates\">\n   <property name=\"text\">\n    <string>Check for Updates...</string>\n   </property>\n  </action>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>GlyphGraphicsView</class>\n   <extends>QGraphicsView</extends>\n   <header>glyphgraphicsview.h</header>\n  </customwidget>\n </customwidgets>\n <resources>\n  <include location=\"assets.qrc\"/>\n </resources>\n <connections>\n  <connection>\n   <sender>showNonExportedGlyphsCheckBox</sender>\n   <signal>toggled(bool)</signal>\n   <receiver>actionShow_non_exported_Glyphs</receiver>\n   <slot>setChecked(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>563</x>\n     <y>508</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionShow_non_exported_Glyphs</sender>\n   <signal>toggled(bool)</signal>\n   <receiver>showNonExportedGlyphsCheckBox</receiver>\n   <slot>setChecked(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>563</x>\n     <y>508</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n <buttongroups>\n  <buttongroup name=\"exportMethodButtonGroup\"/>\n </buttongroups>\n</ui>\n"
  },
  {
    "path": "app/mainwindowmodel.cpp",
    "content": "#include \"mainwindowmodel.h\"\n#include \"sourcecoderunnable.h\"\n#include \"f2b_qt_compat.h\"\n#include <f2b.h>\n\n#include <QDebug>\n#include <QThreadPool>\n#include <QFile>\n#include <QDataStream>\n#include <QDir>\n\n#include <iostream>\n#include <thread>\n\nQ_DECLARE_METATYPE(f2b::source_code_options::bit_numbering_type);\nQ_DECLARE_METATYPE(f2b::source_code_options::export_method_type);\n\nnamespace SettingsKey {\nstatic const QString showNonExportedGlyphs = \"main_window/show_non_expoerted_glyphs\";\nstatic const QString exportMethod = \"source_code_options/export_method\";\nstatic const QString bitNumbering = \"source_code_options/bit_numbering\";\nstatic const QString invertBits = \"source_code_options/invert_bits\";\nstatic const QString includeLineSpacing = \"source_code_options/include_line_spacing\";\nstatic const QString format = \"source_code_options/format\";\nstatic const QString indentation = \"source_code_options/indentation\";\nstatic const QString documentPath = \"source_code_options/document_path\";\nstatic const QString lastDocumentDirectory = \"source_code_options/last_document_directory\";\nstatic const QString lastSourceCodeDirectory = \"source_code_options/last_source_code_directory\";\n}\n\nbool operator==(const UIState& lhs, const UIState& rhs)\n{\n    return lhs.actions == rhs.actions &&\n            lhs.statusBarMessage == rhs.statusBarMessage &&\n            lhs.lastUserAction == rhs.lastUserAction &&\n            lhs.selectedTab == rhs.selectedTab;\n}\n\nbool operator!=(const UIState& lhs, const UIState& rhs)\n{\n    return !(lhs == rhs);\n}\n\nMainWindowModel::MainWindowModel(QObject *parent) :\n    QObject(parent)\n{\n    shouldShowNonExportedGlyphs_ = settings_.value(SettingsKey::showNonExportedGlyphs, true).toBool();\n\n    sourceCodeOptions_.export_method =\n            qvariant_cast<f2b::source_code_options::export_method_type>(\n                settings_.value(SettingsKey::exportMethod, f2b::source_code_options::export_selected)\n                );\n    sourceCodeOptions_.bit_numbering =\n            qvariant_cast<f2b::source_code_options::bit_numbering_type>(\n                settings_.value(SettingsKey::bitNumbering, f2b::source_code_options::lsb)\n                );\n    sourceCodeOptions_.invert_bits = settings_.value(SettingsKey::invertBits, false).toBool();\n    sourceCodeOptions_.include_line_spacing = settings_.value(SettingsKey::includeLineSpacing, false).toBool();\n    sourceCodeOptions_.indentation = from_qvariant(settings_.value(SettingsKey::indentation, to_qvariant(f2b::source_code::tab {})));\n\n    formats_.insert(QString::fromStdString(std::string(f2b::format::c::identifier)), \"C/C++\");\n    formats_.insert(QString::fromStdString(std::string(f2b::format::arduino::identifier)), \"Arduino\");\n    formats_.insert(QString::fromStdString(std::string(f2b::format::python_list::identifier)), \"Python List\");\n    formats_.insert(QString::fromStdString(std::string(f2b::format::python_bytes::identifier)), \"Python Bytes\");\n\n    currentFormat_ = settings_.value(SettingsKey::format, formats_.firstKey()).toString();\n\n    indentationStyles_.push_back({ f2b::source_code::tab {}, tr(\"Tab\") });\n    for (std::size_t i = 1; i <= 8; ++i) {\n        indentationStyles_.push_back({ f2b::source_code::space {i}, tr(\"%n Space(s)\", \"\", i) });\n    }\n\n    connect(this, &MainWindowModel::runnableFinished,\n            this, &MainWindowModel::sourceCodeChanged,\n            Qt::BlockingQueuedConnection);\n\n    qDebug() << \"output format:\" << currentFormat_;\n}\n\nvoid MainWindowModel::restoreSession()\n{\n    auto path = settings_.value(SettingsKey::documentPath).toString();\n    if (!path.isNull()) {\n        openDocument(path, true);\n    } else {\n        updateDocumentTitle();\n    }\n}\n\nvoid MainWindowModel::registerInputEvent(InputEvent e)\n{\n    auto state { uiState_ };\n    if (std::holds_alternative<UIState::InterfaceAction>(e)) {\n        auto action = std::get<UIState::InterfaceAction>(e);\n        switch (action) {\n        case UIState::ActionTabEdit:\n            state.selectedTab = UIState::TabEdit;\n            break;\n        case UIState::ActionTabCode:\n            state.selectedTab = UIState::TabCode;\n            break;\n        default:\n            break;\n        }\n    } else if (std::holds_alternative<UIState::UserAction>(e)) {\n        auto action = std::get<UIState::UserAction>(e);\n        state.lastUserAction = action;\n        switch (action) {\n        case UIState::UserIdle:\n            state.statusBarMessage = UIState::MessageIdle;\n            state.actions.reset();\n            break;\n        case UIState::UserLoadedDocument:\n            state.statusBarMessage = UIState::MessageLoadedFace;\n            state.actions.reset();\n            state.actions.set(UIState::ActionAddGlyph);\n            state.actions.set(UIState::ActionSave);\n            state.actions.set(UIState::ActionClose);\n            state.actions.set(UIState::ActionPrint);\n            state.actions.set(UIState::ActionExport);\n            state.actions.set(UIState::ActionTabCode);\n            break;\n        case UIState::UserLoadedGlyph:\n            state.statusBarMessage = UIState::MessageLoadedGlyph;\n            state.actions.set(UIState::ActionDeleteGlyph);\n            state.actions.set(UIState::ActionCopy);\n            break;\n        }\n        state.actions.set(UIState::ActionTabEdit);\n    }\n\n    if (state != uiState_) {\n        uiState_ = state;\n        emit uiStateChanged(uiState_);\n    }\n}\n\nvoid MainWindowModel::updateDocumentTitle()\n{\n    QString name;\n    if (documentPath_.has_value()) {\n        QFileInfo fileInfo {documentPath_.value()};\n        name = fileInfo.fileName();\n    } else {\n        name = tr(\"New Document\");\n    }\n\n    if (auto faceModel = fontFaceViewModel_.get()) {\n        if (faceModel->isModifiedSinceSave()) {\n            name += \" - \";\n            name += tr(\"Edited\");\n        }\n    }\n    if (name != documentTitle_) {\n        documentTitle_ = name;\n        emit documentTitleChanged(documentTitle_);\n    }\n}\n\nvoid MainWindowModel::importFont(const QFont &font)\n{\n    fontFaceViewModel_ = std::make_unique<FontFaceViewModel>(font);\n    registerInputEvent(UIState::UserLoadedDocument);\n    setDocumentPath({});\n    emit faceLoaded(fontFaceViewModel_->face());\n    updateDocumentTitle();\n}\n\nvoid MainWindowModel::openDocument(const QString &fileName)\n{\n    openDocument(fileName, false);\n}\n\nvoid MainWindowModel::openDocument(const QString &fileName, bool failSilently)\n{\n    try {\n        fontFaceViewModel_ = std::make_unique<FontFaceViewModel>(fileName);\n\n        qDebug() << \"face loaded from\" << fileName;\n\n        registerInputEvent(UIState::UserLoadedDocument);\n        setDocumentPath(fileName);\n        updateDocumentTitle();\n        emit faceLoaded(fontFaceViewModel_->face());\n\n    } catch (std::runtime_error& e) {\n        setDocumentPath({});\n        updateDocumentTitle();\n\n        qCritical() << e.what();\n\n        if (!failSilently) {\n            emit documentError(QString::fromStdString(e.what()));\n        }\n    }\n}\n\nvoid MainWindowModel::saveDocument(const QString& fileName)\n{\n    try {\n        fontFaceViewModel_->saveToFile(fileName);\n\n        qDebug() << \"face saved to\" << fileName;\n\n        setDocumentPath(fileName);\n        updateDocumentTitle();\n    } catch (std::runtime_error& e) {\n        qCritical() << e.what();\n        emit documentError(QString::fromStdString(e.what()));\n    }\n}\n\nvoid MainWindowModel::closeCurrentDocument()\n{\n    fontFaceViewModel_.release();\n    setDocumentPath({});\n    updateDocumentTitle();\n    registerInputEvent(UIState::UserIdle);\n    emit documentClosed();\n}\n\nvoid MainWindowModel::setActiveGlyphIndex(std::optional<std::size_t> index)\n{\n    if (fontFaceViewModel_->activeGlyphIndex().has_value() and\n            fontFaceViewModel_->activeGlyphIndex().value() == index)\n    {\n        return;\n    }\n\n    try {\n        fontFaceViewModel_->setActiveGlyphIndex(index);\n        registerInputEvent(UIState::UserLoadedGlyph);\n\n        emit activeGlyphChanged(fontFaceViewModel_->activeGlyph());\n    } catch (const std::exception& e) {\n        qCritical() << e.what();\n    }\n}\n\nvoid MainWindowModel::setShouldShowNonExportedGlyphs(bool enabled)\n{\n    shouldShowNonExportedGlyphs_ = enabled;\n    settings_.setValue(SettingsKey::showNonExportedGlyphs, enabled);\n}\n\nvoid MainWindowModel::setExportAllEnabled(bool enabled)\n{\n    auto exportMethod = enabled ? f2b::source_code_options::export_all : f2b::source_code_options::export_selected;\n    sourceCodeOptions_.export_method = exportMethod;\n    settings_.setValue(SettingsKey::exportMethod, exportMethod);\n    reloadSourceCode();\n}\n\nvoid MainWindowModel::setInvertBits(bool enabled)\n{\n    sourceCodeOptions_.invert_bits = enabled;\n    settings_.setValue(SettingsKey::invertBits, enabled);\n    reloadSourceCode();\n}\n\nvoid MainWindowModel::setMSBEnabled(bool enabled)\n{\n    auto bitNumbering = enabled ? f2b::source_code_options::msb : f2b::source_code_options::lsb;\n    sourceCodeOptions_.bit_numbering = bitNumbering;\n    settings_.setValue(SettingsKey::bitNumbering, bitNumbering);\n    reloadSourceCode();\n}\n\nvoid MainWindowModel::setIncludeLineSpacing(bool enabled)\n{\n    sourceCodeOptions_.include_line_spacing = enabled;\n    settings_.setValue(SettingsKey::includeLineSpacing, enabled);\n    reloadSourceCode();\n}\n\nvoid MainWindowModel::setOutputFormat(const QString& format)\n{\n    currentFormat_ = formats_.key(format, formats_.first());\n    settings_.setValue(SettingsKey::format, currentFormat_);\n    reloadSourceCode();\n}\n\nvoid MainWindowModel::setIndentation(const QString &indentationLabel)\n{\n    auto i = std::find_if(indentationStyles_.cbegin(), indentationStyles_.cend(), [&](const auto& pair) -> bool {\n        return pair.second == indentationLabel;\n    });\n    if (i != indentationStyles_.end()) {\n        sourceCodeOptions_.indentation = i->first;\n        settings_.setValue(SettingsKey::indentation, to_qvariant(i->first));\n        reloadSourceCode();\n    }\n}\n\nQString MainWindowModel::indentationStyleCaption() const\n{\n    auto i = std::find_if(indentationStyles_.cbegin(), indentationStyles_.cend(), [&](const auto& pair) -> bool {\n        return pair.first == sourceCodeOptions_.indentation;\n    });\n    if (i != indentationStyles_.end()) {\n        return i->second;\n    }\n    return indentationStyles_.front().second;\n}\n\nvoid MainWindowModel::setDocumentPath(const std::optional<QString>& path)\n{\n    documentPath_ = path;\n    if (path.has_value()) {\n        settings_.setValue(SettingsKey::documentPath, path.value());\n        setLastVisitedDirectory(path.value());\n    } else {\n        settings_.remove(SettingsKey::documentPath);\n    }\n}\n\nQString MainWindowModel::lastVisitedDirectory() const\n{\n    return settings_.value(SettingsKey::lastDocumentDirectory).toString();\n}\n\nvoid MainWindowModel::setLastVisitedDirectory(const QString& path)\n{\n    settings_.setValue(SettingsKey::lastDocumentDirectory, QFileInfo(path).path());\n}\n\nQString MainWindowModel::lastSourceCodeDirectory() const\n{\n    return settings_.value(SettingsKey::lastSourceCodeDirectory).toString();\n}\n\nvoid MainWindowModel::setLastSourceCodeDirectory(const QString& path)\n{\n    settings_.setValue(SettingsKey::lastSourceCodeDirectory, QFileInfo(path).path());\n}\n\nvoid MainWindowModel::reloadSourceCode()\n{\n    /// WIP :)\n    emit sourceCodeUpdating();\n\n    auto r = new SourceCodeRunnable { faceModel()->face(), sourceCodeOptions_, currentFormat_, fontArrayName_ };\n    r->setCompletionHandler([&](const QString& output) {\n        qDebug() << \"Source code size:\" << output.size() << \"bytes\";\n        std::scoped_lock { sourceCodeMutex_ };\n        sourceCode_ = std::move(output);\n        emit runnableFinished();\n    });\n    r->setAutoDelete(true);\n\n    QThreadPool::globalInstance()->start(r);\n}\n\nvoid MainWindowModel::resetGlyph(std::size_t index)\n{\n    fontFaceViewModel_->resetGlyph(index);\n    reloadSourceCode();\n}\n\nvoid MainWindowModel::modifyGlyph(std::size_t index, const f2b::font::glyph &new_glyph)\n{\n    fontFaceViewModel_->modifyGlyph(index, new_glyph);\n    reloadSourceCode();\n}\n\nvoid MainWindowModel::modifyGlyph(std::size_t index,\n                                  const BatchPixelChange &change,\n                                  BatchPixelChange::ChangeType changeType)\n{\n    fontFaceViewModel_->modifyGlyph(index, change, changeType);\n    reloadSourceCode();\n}\n\nvoid MainWindowModel::appendGlyph(f2b::font::glyph glyph)\n{\n    fontFaceViewModel_->appendGlyph(std::move(glyph));\n    reloadSourceCode();\n}\n\nvoid MainWindowModel::deleteGlyph(std::size_t index)\n{\n    fontFaceViewModel_->deleteGlyph(index);\n\n    if (index == fontFaceViewModel_->face().num_glyphs() - 1) {\n        fontFaceViewModel_->setActiveGlyphIndex(index - 1);\n        emit activeGlyphChanged(fontFaceViewModel_->activeGlyph());\n    }\n\n    reloadSourceCode();\n}\n\nvoid MainWindowModel::setGlyphExported(std::size_t index, bool isExported)\n{\n    fontFaceViewModel_->setGlyphExportedState(index, isExported);\n    reloadSourceCode();\n}\n"
  },
  {
    "path": "app/mainwindowmodel.h",
    "content": "#ifndef MAINWINDOWMODEL_H\n#define MAINWINDOWMODEL_H\n\n#include \"fontfaceviewmodel.h\"\n#include <f2b.h>\n#include <memory>\n#include <functional>\n#include <optional>\n#include <bitset>\n#include <variant>\n#include <vector>\n#include <mutex>\n\n#include <QMap>\n#include <QSettings>\n\nstruct UIState {\n    enum InterfaceAction {\n        ActionAddGlyph = 0,\n        ActionDeleteGlyph,\n        ActionSave,\n        ActionClose,\n        ActionCopy,\n        ActionPaste,\n        ActionPrint,\n        ActionExport,\n        ActionTabEdit,\n        ActionTabCode,\n        ActionCount,\n        ActionFirst = ActionAddGlyph\n    };\n\n    enum UserAction {\n        UserIdle = 0,\n        UserLoadedDocument,\n        UserLoadedGlyph,\n    };\n\n    enum Message {\n        MessageIdle = 0,\n        MessageLoadedFace,\n        MessageLoadedGlyph\n    };\n\n    enum Tab {\n        TabEdit,\n        TabCode\n    };\n\n    std::bitset<ActionCount> actions;\n    UserAction lastUserAction;\n    Message statusBarMessage;\n    Tab selectedTab;\n};\n\n\nclass MainWindowModel: public QObject\n{\n    Q_OBJECT\n\npublic:\n\n    using InputEvent = std::variant<UIState::InterfaceAction,UIState::UserAction>;\n\n    explicit MainWindowModel(QObject *parent = nullptr);\n    void restoreSession();\n\n    FontFaceViewModel* faceModel() const {\n        return fontFaceViewModel_.get();\n    }\n\n    const UIState& uiState() const { return uiState_; }\n\n    Qt::CheckState shouldShowNonExportedGlyphs() const {\n        return shouldShowNonExportedGlyphs_ ? Qt::Checked : Qt::Unchecked;\n    }\n\n    bool exportAllEnabled() const {\n        return sourceCodeOptions_.export_method == f2b::source_code_options::export_method_type::export_all\n                ? Qt::Checked : Qt::Unchecked;\n    }\n\n    Qt::CheckState invertBits() const {\n        return sourceCodeOptions_.invert_bits ? Qt::Checked : Qt::Unchecked;\n    }\n\n    Qt::CheckState msbEnabled() const {\n        return sourceCodeOptions_.bit_numbering == f2b::source_code_options::bit_numbering_type::msb\n                ? Qt::Checked : Qt::Unchecked;\n    }\n\n    Qt::CheckState includeLineSpacing() const {\n        return sourceCodeOptions_.include_line_spacing ? Qt::Checked : Qt::Unchecked;\n    }\n\n    const QMap<QString,QString>& outputFormats() const {\n        return formats_;\n    }\n\n    QString outputFormat() const {\n        return formats_.value(currentFormat_, formats_.first());\n    }\n\n    const std::vector<std::pair<f2b::source_code::indentation, QString>>& indentationStyles() const {\n        return indentationStyles_;\n    }\n\n    QString indentationStyleCaption() const;\n\n    void registerInputEvent(InputEvent e);\n\n    const std::optional<QString>& currentDocumentPath() const {\n        return documentPath_;\n    }\n\n    QString documentTitle() const { return documentTitle_; }\n    void updateDocumentTitle();\n\n    void setFontArrayName(const QString& fontArrayName) {\n        if (fontArrayName_ != fontArrayName) {\n            fontArrayName_ = fontArrayName;\n            reloadSourceCode();\n        }\n    }\n\n    QString lastVisitedDirectory() const;\n\n    QString lastSourceCodeDirectory() const;\n    void setLastSourceCodeDirectory(const QString& path);\n\n    void resetGlyph(std::size_t index);\n    void modifyGlyph(std::size_t index, const f2b::font::glyph &new_glyph);\n    void modifyGlyph(std::size_t index,\n                     const BatchPixelChange &change,\n                     BatchPixelChange::ChangeType changeType);\n    void appendGlyph(f2b::font::glyph glyph);\n    void deleteGlyph(std::size_t index);\n    void setGlyphExported(std::size_t index, bool isExported);\n\n    const QString& sourceCode() {\n        std::scoped_lock { sourceCodeMutex_ };\n        return sourceCode_;\n    }\n\npublic slots:\n    void importFont(const QFont& font);\n\n    void openDocument(const QString& fileName);\n    void saveDocument(const QString& fileName);\n    void closeCurrentDocument();\n\n    void setActiveGlyphIndex(std::optional<std::size_t> index);\n    void setShouldShowNonExportedGlyphs(bool enabled);\n    void setExportAllEnabled(bool enabled);\n    void setInvertBits(bool enabled);\n    void setMSBEnabled(bool enabled);\n    void setIncludeLineSpacing(bool enabled);\n    void setOutputFormat(const QString &format); // human-readable\n    void setIndentation(const QString &indentationLabel); // human-readable\n\nsignals:\n    void uiStateChanged(UIState state) const;\n    void faceLoaded(f2b::font::face& face) const;\n    void activeGlyphChanged(std::optional<f2b::font::glyph> glyph) const;\n    void sourceCodeUpdating() const;\n    void sourceCodeChanged() const;\n    void runnableFinished() const;\n    void documentTitleChanged(const QString& title);\n    void documentClosed();\n    void documentError(const QString& error);\n\nprivate:\n    void reloadSourceCode();\n    void setDocumentPath(const std::optional<QString>& path);\n    void setLastVisitedDirectory(const QString& path);\n    void openDocument(const QString& fileName, bool failSilently);\n\n    UIState uiState_ {};\n    std::unique_ptr<FontFaceViewModel> fontFaceViewModel_;\n    std::optional<QString> documentPath_;\n    QString documentTitle_;\n    QString fontArrayName_;\n    f2b::source_code_options sourceCodeOptions_;\n    bool shouldShowNonExportedGlyphs_;\n\n    QString sourceCode_;\n    std::mutex sourceCodeMutex_;\n\n    QMap<QString, QString> formats_; // identifier <-> human-readable\n    QString currentFormat_; // identifier\n    std::vector<std::pair<f2b::source_code::indentation, QString>> indentationStyles_;\n    QSettings settings_;\n};\n\n#endif // MAINWINDOWMODEL_H\n"
  },
  {
    "path": "app/qfontfacereader.cpp",
    "content": "#include \"qfontfacereader.h\"\n#include \"f2b_qt_compat.h\"\n\n#include <QAbstractTextDocumentLayout>\n#include <QFontMetrics>\n#include <QImage>\n#include <QPainter>\n#include <QTextDocument>\n#include <QTextFrame>\n#include \"utf8.h\"\n\nusing namespace std::literals::string_view_literals;\n\nstatic constexpr std::string_view ascii_glyphs =\n        \" !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"sv;\n\nQFontFaceReader::QFontFaceReader(const QFont &font, std::string text, std::optional<f2b::font::glyph_size> forced_size) :\n    f2b::font::face_reader()\n{\n    std::string source_text { text.empty() ? ascii_glyphs : std::move(text) };\n    num_glyphs_ = source_text.length();\n\n    auto result = read_font(font, std::move(source_text), forced_size);\n    sz_ = result.first;\n    font_image_ = std::move(result.second);\n}\n\nbool QFontFaceReader::is_pixel_set(std::size_t glyph_id, f2b::font::point p) const\n{\n    p.y += glyph_id * sz_.height;\n    return font_image_->pixelColor(f2b::font::qpoint_with_point(p)) == Qt::color1;\n}\n\nQString QFontFaceReader::template_text(std::string text)\n{\n    std::stringstream stream;\n\n    utf8::iterator i(text.begin(), text.begin(), text.end());\n    utf8::iterator end(text.end(), text.begin(), text.end());\n\n    while (i != end) {\n        auto utf8_begin = i;\n        auto utf8_end = ++i;\n        stream << std::string(utf8_begin.base(), utf8_end.base()) << \"\\n\";\n    }\n\n    return QString::fromStdString(stream.str());\n}\n\nstd::pair<f2b::font::glyph_size, std::unique_ptr<QImage>> QFontFaceReader::read_font(\n            const QFont &font,\n            std::string text,\n            std::optional<f2b::font::glyph_size> forced_size)\n{\n    auto text_length = text.length();\n    auto template_text = QFontFaceReader::template_text(std::move(text));\n\n    QFontMetrics fm(font);\n    qDebug() << font << fm.height() << fm.maxWidth() << fm.leading() << fm.lineSpacing();\n\n    int width = [&] {\n        if (forced_size.has_value()) {\n            return static_cast<int>(forced_size.value().width);\n        }\n        return fm.boundingRect(QRect(), Qt::AlignLeft, template_text).width();\n    }();\n    int height = [&] {\n        if (forced_size.has_value()) {\n            return static_cast<int>(forced_size.value().height);\n        }\n        return fm.lineSpacing();\n    }();\n\n    QSize img_size(width, height * text_length);\n\n//    qDebug() << \"img size\" << img_size;\n\n    auto image = std::make_unique<QImage>(img_size, QImage::Format::Format_Mono);\n    QPainter p(image.get());\n    p.fillRect(QRect(QPoint(), img_size), QColor(Qt::color0));\n\n    QTextDocument doc;\n    doc.useDesignMetrics();\n    doc.documentLayout()->setPaintDevice(image.get());\n    doc.setDefaultFont(font);\n    doc.setDocumentMargin(0);\n    doc.setPlainText(template_text);\n    doc.setTextWidth(img_size.width());\n\n    QTextFrame *rootFrame = doc.rootFrame();\n    for (auto it = rootFrame->begin(); !(it.atEnd()); ++it) {\n        auto block = it.currentBlock();\n        QTextCursor cursor(block);\n        auto blockFormat = block.blockFormat();\n        blockFormat.setLineHeight(fm.lineSpacing(), QTextBlockFormat::FixedHeight);\n        cursor.setBlockFormat(blockFormat);\n    }\n\n    QAbstractTextDocumentLayout::PaintContext ctx;\n    ctx.palette.setColor(QPalette::Text, Qt::color1);\n\n    doc.documentLayout()->draw(&p, ctx);\n\n    p.end();\n\n//    image->save(\"output.bmp\");\n\n    return { f2b::font::size_with_qsize(QSize(width, fm.lineSpacing())), std::move(image) };\n}\n\n"
  },
  {
    "path": "app/qfontfacereader.h",
    "content": "#ifndef QFONTFACEREADER_H\n#define QFONTFACEREADER_H\n\n#include \"fontdata.h\"\n#include <QFont>\n#include <QImage>\n#include <memory>\n#include <utility>\n#include <optional>\n\nclass QFontFaceReader : public f2b::font::face_reader\n{\npublic:\n    explicit QFontFaceReader(const QFont &font, std::string text = {}, std::optional<f2b::font::glyph_size> forced_size = {});\n    virtual ~QFontFaceReader() override = default;\n\n    virtual f2b::font::glyph_size font_size() const override { return sz_; }\n    virtual std::size_t num_glyphs() const override { return num_glyphs_; }\n    virtual bool is_pixel_set(std::size_t glyph_id, f2b::font::point p) const override;\n\nprivate:\n    static QString template_text(std::string text);\n    static std::pair<f2b::font::glyph_size, std::unique_ptr<QImage>> read_font(\n            const QFont &font, std::string text, std::optional<f2b::font::glyph_size> forcedSize);\n\n    f2b::font::glyph_size sz_ { 0, 0 };\n    std::unique_ptr<QImage> font_image_ { nullptr };\n    std::size_t num_glyphs_ { 0 };\n};\n\n#endif // QFONTFACEREADER_H\n"
  },
  {
    "path": "app/semver.hpp",
    "content": "//          _____                            _   _\n//         / ____|                          | | (_)\n//        | (___   ___ _ __ ___   __ _ _ __ | |_ _  ___\n//         \\___ \\ / _ \\ '_ ` _ \\ / _` | '_ \\| __| |/ __|\n//         ____) |  __/ | | | | | (_| | | | | |_| | (__\n//        |_____/ \\___|_| |_| |_|\\__,_|_| |_|\\__|_|\\___|\n// __      __           _             _                _____\n// \\ \\    / /          (_)           (_)              / ____|_     _\n//  \\ \\  / /__ _ __ ___ _  ___  _ __  _ _ __   __ _  | |   _| |_ _| |_\n//   \\ \\/ / _ \\ '__/ __| |/ _ \\| '_ \\| | '_ \\ / _` | | |  |_   _|_   _|\n//    \\  /  __/ |  \\__ \\ | (_) | | | | | | | | (_| | | |____|_|   |_|\n//     \\/ \\___|_|  |___/_|\\___/|_| |_|_|_| |_|\\__, |  \\_____|\n// https://github.com/Neargye/semver           __/ |\n// version 0.2.2                              |___/\n//\n// Licensed under the MIT License <http://opensource.org/licenses/MIT>.\n// SPDX-License-Identifier: MIT\n// Copyright (c) 2018 - 2020 Daniil Goncharov <neargye@gmail.com>.\n//\n// Permission is hereby  granted, free of charge, to any  person obtaining a copy\n// of this software and associated  documentation files (the \"Software\"), to deal\n// in the Software  without restriction, including without  limitation the rights\n// to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell\n// copies  of  the Software,  and  to  permit persons  to  whom  the Software  is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE  IS PROVIDED \"AS  IS\", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR\n// IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,\n// FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE\n// AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER\n// LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n#ifndef NEARGYE_SEMANTIC_VERSIONING_HPP\n#define NEARGYE_SEMANTIC_VERSIONING_HPP\n\n#include <cstdint>\n#include <cstddef>\n#include <limits>\n#include <iosfwd>\n#include <optional>\n#include <string>\n#include <string_view>\n#if __has_include(<charconv>)\n#include <charconv>\n#else\n#include <system_error>\n#endif\n\n// Allow to disable exceptions.\n#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(SEMVER_NOEXCEPTION)\n#  include <stdexcept>\n#  define __SEMVER_THROW(exception) throw exception\n#else\n#  include <cstdlib>\n#  define __SEMVER_THROW(exception) std::abort()\n#endif\n\n#if defined(__APPLE_CC__)\n#  pragma clang diagnostic push\n#  pragma clang diagnostic ignored \"-Wmissing-braces\" // Ignore warning: suggest braces around initialization of subobject 'return {first, std::errc::invalid_argument};'.\n#endif\n\nnamespace semver {\n\nenum struct prerelease : std::uint8_t {\n  alpha = 0,\n  beta = 1,\n  rc = 2,\n  none = 3\n};\n\n// Max version string length = 3(<major>) + 1(.) + 3(<minor>) + 1(.) + 3(<patch>) + 1(-) + 5(<prerelease>) + 1(.) + 3(<prereleaseversion>) = 21.\ninline constexpr std::size_t max_version_string_length = 21;\n\nnamespace detail {\n\n#if __has_include(<charconv>)\nstruct from_chars_result : std::from_chars_result {\n  [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; }\n};\n\nstruct to_chars_result : std::to_chars_result {\n  [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; }\n};\n#else\nstruct from_chars_result {\n  const char* ptr;\n  std::errc ec;\n\n  [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; }\n};\n\nstruct to_chars_result {\n  char* ptr;\n  std::errc ec;\n\n  [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; }\n};\n#endif\n\ninline constexpr std::string_view alpha = {\"-alpha\", 6};\ninline constexpr std::string_view beta  = {\"-beta\", 5};\ninline constexpr std::string_view rc    = {\"-rc\", 3};\n\n// Min version string length = 1(<major>) + 1(.) + 1(<minor>) + 1(.) + 1(<patch>) = 5.\ninline constexpr auto min_version_string_length = 5;\n\nconstexpr char to_lower(char c) noexcept {\n  return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c;\n}\n\nconstexpr bool is_digit(char c) noexcept {\n  return c >= '0' && c <= '9';\n}\n\nconstexpr std::uint8_t to_digit(char c) noexcept {\n  return c - '0';\n}\n\nconstexpr std::uint8_t length(std::uint8_t x) noexcept {\n  return x < 10 ? 1 : (x < 100 ? 2 : 3);\n}\n\nconstexpr std::uint8_t length(prerelease t) noexcept {\n  if (t == prerelease::alpha) {\n    return 5;\n  } else if (t == prerelease::beta) {\n    return 4;\n  } else if(t == prerelease::rc) {\n    return 2;\n  }\n\n  return 0;\n}\n\nconstexpr bool equals(const char* first, const char* last, std::string_view str) noexcept {\n  for (std::size_t i = 0; first != last && i < str.length(); ++i, ++first) {\n    if (to_lower(*first) != to_lower(str[i])) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nconstexpr char* to_chars(char* str, std::uint8_t x, bool dot = true) noexcept {\n  do {\n    *(--str) = static_cast<char>('0' + (x % 10));\n    x /= 10;\n  } while (x != 0);\n\n  if (dot) {\n    *(--str) = '.';\n  }\n\n  return str;\n}\n\nconstexpr char* to_chars(char* str, prerelease t) noexcept {\n  const auto p = t == prerelease::alpha\n                     ? alpha\n                     : t == prerelease::beta\n                           ? beta\n                           : t == prerelease::rc\n                                 ? rc\n                                 : std::string_view{};\n\n  for (auto it = p.rbegin(); it != p.rend(); ++it) {\n    *(--str) = *it;\n  }\n\n  return str;\n}\n\nconstexpr const char* from_chars(const char* first, const char* last, std::uint8_t& d) noexcept {\n  if (first != last && is_digit(*first)) {\n    std::int32_t t = 0;\n    for (; first != last && is_digit(*first); ++first) {\n      t = t * 10 + to_digit(*first);\n    }\n    if (t <= (std::numeric_limits<std::uint8_t>::max)()) {\n      d = static_cast<std::uint8_t>(t);\n      return first;\n    }\n  }\n\n  return nullptr;\n}\n\nconstexpr const char* from_chars(const char* first, const char* last, prerelease& p) noexcept {\n  if (equals(first, last, alpha)) {\n    p = prerelease::alpha;\n    return first + alpha.length();\n  } else if (equals(first, last, beta)) {\n    p = prerelease::beta;\n    return first + beta.length();\n  } else if (equals(first, last, rc)) {\n    p = prerelease::rc;\n    return first + rc.length();\n  }\n\n  return nullptr;\n}\n\nconstexpr bool check_delimiter(const char* first, const char* last, char d) noexcept {\n  return first != last && first != nullptr && *first == d;\n}\n\n} // namespace semver::detail\n\nstruct version {\n  std::uint8_t major             = 0;\n  std::uint8_t minor             = 1;\n  std::uint8_t patch             = 0;\n  prerelease prerelease_type     = prerelease::none;\n  std::uint8_t prerelease_number = 0;\n\n  constexpr version(std::uint8_t major,\n                    std::uint8_t minor,\n                    std::uint8_t patch,\n                    prerelease prerelease_type = prerelease::none,\n                    std::uint8_t prerelease_number = 0) noexcept\n      : major{major},\n        minor{minor},\n        patch{patch},\n        prerelease_type{prerelease_type},\n        prerelease_number{prerelease_type == prerelease::none ? static_cast<std::uint8_t>(0) : prerelease_number} {\n  }\n\n  explicit constexpr version(std::string_view str) : version(0, 0, 0, prerelease::none, 0) {\n    from_string(str);\n  }\n\n  constexpr version() = default; // https://semver.org/#how-should-i-deal-with-revisions-in-the-0yz-initial-development-phase\n\n  constexpr version(const version&) = default;\n\n  constexpr version(version&&) = default;\n\n  ~version() = default;\n\n  version& operator=(const version&) = default;\n\n  version& operator=(version&&) = default;\n\n  [[nodiscard]] constexpr detail::from_chars_result from_chars(const char* first, const char* last) noexcept {\n    if (first == nullptr || last == nullptr || (last - first) < detail::min_version_string_length) {\n      return {first, std::errc::invalid_argument};\n    }\n\n    auto next = first;\n    if (next = detail::from_chars(next, last, major); detail::check_delimiter(next, last, '.')) {\n      if (next = detail::from_chars(++next, last, minor); detail::check_delimiter(next, last, '.')) {\n        if (next = detail::from_chars(++next, last, patch); next == last) {\n          prerelease_type = prerelease::none;\n          prerelease_number = 0;\n          return {next, std::errc{}};\n        } else if (detail::check_delimiter(next, last, '-')) {\n          if (next = detail::from_chars(next, last, prerelease_type); next == last) {\n            prerelease_number = 0;\n            return {next, std::errc{}};\n          } else if (detail::check_delimiter(next, last, '.')) {\n            if (next = detail::from_chars(++next, last, prerelease_number); next == last) {\n              return {next, std::errc{}};\n            }\n          }\n        }\n      }\n    }\n\n    return {first, std::errc::invalid_argument};\n  }\n\n  [[nodiscard]] constexpr detail::to_chars_result to_chars(char* first, char* last) const noexcept {\n    const auto length = chars_length();\n    if (first == nullptr || last == nullptr || (last - first) < length) {\n      return {last, std::errc::value_too_large};\n    }\n\n    auto next = first + length;\n    if (prerelease_type != prerelease::none) {\n      if (prerelease_number != 0) {\n        next = detail::to_chars(next, prerelease_number);\n      }\n      next = detail::to_chars(next, prerelease_type);\n    }\n    next = detail::to_chars(next, patch);\n    next = detail::to_chars(next, minor);\n    next = detail::to_chars(next, major, false);\n\n    return {first + length, std::errc{}};\n  }\n\n  [[nodiscard]] constexpr bool from_string_noexcept(std::string_view str) noexcept {\n    return from_chars(str.data(), str.data() + str.length());\n  }\n\n  constexpr version& from_string(std::string_view str) {\n    if (!from_string_noexcept(str)) {\n      __SEMVER_THROW(std::invalid_argument{\"semver::version::from_string invalid version.\"});\n    }\n\n    return *this;\n  }\n\n  [[nodiscard]] std::string to_string() const {\n    auto str = std::string(chars_length(), '\\0');\n    if (!to_chars(str.data(), str.data() + str.length())) {\n      __SEMVER_THROW(std::invalid_argument{\"semver::version::to_string invalid version.\"});\n    }\n\n    return str;\n  }\n\n  [[nodiscard]] constexpr int compare(const version& other) const noexcept {\n    if (major != other.major) {\n      return major - other.major;\n    }\n\n    if (minor != other.minor) {\n      return minor - other.minor;\n    }\n\n    if (patch != other.patch) {\n      return patch - other.patch;\n    }\n\n    if (prerelease_type != other.prerelease_type) {\n      return static_cast<std::uint8_t>(prerelease_type) - static_cast<std::uint8_t>(other.prerelease_type);\n    }\n\n    if (prerelease_number != other.prerelease_number) {\n      return prerelease_number - other.prerelease_number;\n    }\n\n    return 0;\n  }\n\n private:\n  constexpr std::uint8_t chars_length() const noexcept {\n    // (<major>) + 1(.) + (<minor>) + 1(.) + (<patch>)\n    std::uint8_t length = detail::length(major) + detail::length(minor) + detail::length(patch) + 2;\n    if (prerelease_type != prerelease::none) {\n      // + 1(-) + (<prerelease>)\n      length += detail::length(prerelease_type) + 1;\n      if (prerelease_number != 0) {\n        // + 1(.) + (<prereleaseversion>)\n        length += detail::length(prerelease_number) + 1;\n      }\n    }\n    return length;\n  }\n};\n\n[[nodiscard]] constexpr bool operator==(const version& lhs, const version& rhs) noexcept {\n  return lhs.compare(rhs) == 0;\n}\n\n[[nodiscard]] constexpr bool operator!=(const version& lhs, const version& rhs) noexcept {\n  return lhs.compare(rhs) != 0;\n}\n\n[[nodiscard]] constexpr bool operator>(const version& lhs, const version& rhs) noexcept {\n  return lhs.compare(rhs) > 0;\n}\n\n[[nodiscard]] constexpr bool operator>=(const version& lhs, const version& rhs) noexcept {\n  return lhs.compare(rhs) >= 0;\n}\n\n[[nodiscard]] constexpr bool operator<(const version& lhs, const version& rhs) noexcept {\n  return lhs.compare(rhs) < 0;\n}\n\n[[nodiscard]] constexpr bool operator<=(const version& lhs, const version& rhs) noexcept {\n  return lhs.compare(rhs) <= 0;\n}\n\n[[nodiscard]] constexpr version operator\"\"_version(const char* str, std::size_t length) {\n  return version{std::string_view{str, length}};\n}\n\n[[nodiscard]] constexpr bool valid(std::string_view str) noexcept {\n  return version{}.from_string_noexcept(str);\n}\n\n[[nodiscard]] constexpr detail::from_chars_result from_chars(const char* first, const char* last, version& v) noexcept {\n  return v.from_chars(first, last);\n}\n\n[[nodiscard]] constexpr detail::to_chars_result to_chars(char* first, char* last, const version& v) noexcept {\n  return v.to_chars(first, last);\n}\n\n[[nodiscard]] constexpr std::optional<version> from_string_noexcept(std::string_view str) noexcept {\n  if (auto v = version{}; v.from_string_noexcept(str)) {\n    return v;\n  }\n\n  return std::nullopt;\n}\n\n[[nodiscard]] constexpr version from_string(std::string_view str) {\n  return version{str};\n}\n\n[[nodiscard]] inline std::string to_string(const version& v) {\n  return v.to_string();\n}\n\ntemplate <typename Char, typename Traits>\ninline std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, const version& v) {\n  for (const auto c : v.to_string()) {\n    os.put(c);\n  }\n\n  return os;\n}\n\nnamespace ranges {\n\n} // namespace semver::ranges\n\n} // namespace semver\n\n#if defined(__APPLE_CC__)\n#  pragma clang diagnostic pop\n#endif\n\n#endif // NEARGYE_SEMANTIC_VERSIONING_HPP\n"
  },
  {
    "path": "app/sourcecoderunnable.cpp",
    "content": "#include \"sourcecoderunnable.h\"\n#include <QElapsedTimer>\n#include <QDebug>\n\nvoid SourceCodeRunnable::run()\n{\n    QString output;\n\n    QElapsedTimer timer;\n    timer.start();\n    if (format_ == f2b::format::arduino::identifier) {\n        output = QString::fromStdString(generator_.generate<f2b::format::arduino>(face_, fontArrayName_));\n    } else if (format_ == f2b::format::c::identifier) {\n        output = QString::fromStdString(generator_.generate<f2b::format::c>(face_, fontArrayName_));\n    } else if (format_ == f2b::format::python_list::identifier) {\n        output = QString::fromStdString(generator_.generate<f2b::format::python_list>(face_, fontArrayName_));\n    } else if (format_ == f2b::format::python_bytes::identifier) {\n        output = QString::fromStdString(generator_.generate<f2b::format::python_bytes>(face_, fontArrayName_));\n    }\n    qDebug() << \"Generation finished in\" << timer.elapsed() << \"ms\";\n\n    setFinished(true);\n    if (!isCanceled()) {\n        handler_(output);\n    }\n}\n\nbool SourceCodeRunnable::isFinished()\n{\n    std::scoped_lock lock { mutex_ };\n    return m_finished;\n}\n\nbool SourceCodeRunnable::isCanceled()\n{\n    std::scoped_lock lock { mutex_ };\n    return m_canceled;\n}\n\nvoid SourceCodeRunnable::setCanceled(bool canceled)\n{\n    std::scoped_lock lock { mutex_ };\n    m_canceled = canceled;\n}\n\nvoid SourceCodeRunnable::setFinished(bool finished)\n{\n    std::scoped_lock lock { mutex_ };\n    m_finished = finished;\n}\n"
  },
  {
    "path": "app/sourcecoderunnable.h",
    "content": "#ifndef SOURCECODERUNNABLE_H\n#define SOURCECODERUNNABLE_H\n\n#include <QRunnable>\n#include <QString>\n#include <f2b.h>\n#include <memory>\n#include <functional>\n#include <optional>\n#include <mutex>\n\nclass SourceCodeRunnable : public QRunnable\n{\n    using CompletionHandler = std::function<void(const QString&)>;\n\npublic:\n    SourceCodeRunnable(f2b::font::face face, f2b::source_code_options options,\n                       const QString& format, const QString& fontArrayName)\n        : QRunnable(),\n          face_ { std::move(face) },\n          generator_ { options },\n          format_ { format.toStdString() },\n          fontArrayName_ { fontArrayName.toStdString() }\n    {};\n\n    void run() override;\n\n    void setCompletionHandler(CompletionHandler handler) {\n        handler_ = std::move(handler);\n    }\n\n    bool isFinished();\n\n    bool isCanceled();\n    void setCanceled(bool canceled);\n\nprotected:\n    void setFinished(bool finished);\n\nprivate:\n    bool m_finished { false };\n    bool m_canceled { false };\n    std::mutex mutex_;\n\n    f2b::font::face face_;\n    f2b::font_source_code_generator generator_;\n    std::string format_;\n    std::string fontArrayName_;\n    CompletionHandler handler_ {};\n};\n\n#endif // SOURCECODERUNNABLE_H\n"
  },
  {
    "path": "app/ui/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.9)\n\nproject(ui LANGUAGES CXX)\n\nfind_package(Qt5 COMPONENTS Core Gui Widgets REQUIRED)\n\nadd_library(ui\n    aboutdialog.cpp\n    aboutdialog.h\n    aboutdialog.ui\n    batchpixelchange.h\n    facewidget.cpp\n    facewidget.h\n    focuswidget.cpp\n    focuswidget.h\n    glyphgraphicsview.cpp\n    glyphgraphicsview.h\n    glyphinfowidget.cpp\n    glyphinfowidget.h\n    glyphwidget.cpp\n    glyphwidget.h\n)\n\ntarget_link_libraries(ui PRIVATE Qt5::Widgets Qt5::Core common font2bytes)\n"
  },
  {
    "path": "app/ui/aboutdialog.cpp",
    "content": "#include \"aboutdialog.h\"\n#include \"./ui_aboutdialog.h\"\n#include <QIcon>\n#include <QDesktopServices>\n#include <QUrl>\n\nstatic constexpr auto about = R\"(<html>\n             <head/>\n             <body>\n                <p><span style=\"font-size:18pt;\"><b>FontEdit </b></span> v##version## build ##build##</p>\n                <p>\n                    Copyright ##year## Dominik Kapusta<br/>\n                    <a href=\"https://kapusta.cc\">https://kapusta.cc</a>\n                    •\n                    <a href=\"https://twitter.com/ayoy\">@ayoy</a>\n                </p>\n                <p align=\"center\">\n                    <br/>\n                    <a href=\"https://github.com/ayoy/fontedit\">Get the Source Code</a>\n                    •\n                    <a href=\"https://github.com/ayoy/fontedit/issues\">Report a Bug</a>\n                    <br/>\n                </p>\n                <p>This program is distributed under the terms of\n                    <a href=\"https://www.gnu.org/licenses/gpl-3.0.en.html\">\n                    General Public License v3</a>.\n                </p>\n                <p>Icons from <a href=\"https://www.flaticon.com/\">www.flaticon.com</a> made by:<br/>\n                    <a href=\"https://www.flaticon.com/authors/smashicons\">Smashicons</a>\n                    •\n                    <a href=\"https://www.flaticon.com/authors/freepik\">Freepik</a>\n                    •\n                    <a href=\"https://www.flaticon.com/authors/pixel-perfect\">Pixel perfect</a>\n                </p>\n             </body>\n             </html>)\";\n\nAboutDialog::AboutDialog(QWidget *parent) :\n    QDialog(parent),\n    ui(new Ui::AboutDialog)\n{\n    ui->setupUi(this);\n    QIcon icon(\":/icon/assets/icon/fontedit96.png\");\n    ui->iconLabel->setPixmap(icon.pixmap(ui->iconLabel->size()));\n    auto text = QString::fromStdString({about});\n    text.replace(\"##version##\", QApplication::applicationVersion());\n    text.replace(\"##build##\", BUILD);\n    text.replace(\"##year##\", YEAR);\n\n    connect(ui->htmlLabel, &QLabel::linkActivated, [&](const QString& link) {\n        QDesktopServices::openUrl(QUrl(link));\n    });\n\n    ui->htmlLabel->setText(text);\n}\n\nAboutDialog::~AboutDialog()\n{\n    delete ui;\n}\n"
  },
  {
    "path": "app/ui/aboutdialog.h",
    "content": "#ifndef ABOUTDIALOG_H\n#define ABOUTDIALOG_H\n\n#include <QDialog>\n\nnamespace Ui {\nclass AboutDialog;\n}\n\nclass AboutDialog : public QDialog\n{\n    Q_OBJECT\n\npublic:\n    explicit AboutDialog(QWidget *parent = nullptr);\n    ~AboutDialog();\n\nprivate:\n    Ui::AboutDialog *ui;\n};\n\n#endif // ABOUTDIALOG_H\n"
  },
  {
    "path": "app/ui/aboutdialog.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>AboutDialog</class>\n <widget class=\"QDialog\" name=\"AboutDialog\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>425</width>\n    <height>300</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>About FontEdit</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\" stretch=\"1,100\">\n     <item>\n      <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n       <property name=\"spacing\">\n        <number>0</number>\n       </property>\n       <item>\n        <widget class=\"QLabel\" name=\"iconLabel\">\n         <property name=\"sizePolicy\">\n          <sizepolicy hsizetype=\"Fixed\" vsizetype=\"Fixed\">\n           <horstretch>0</horstretch>\n           <verstretch>0</verstretch>\n          </sizepolicy>\n         </property>\n         <property name=\"minimumSize\">\n          <size>\n           <width>96</width>\n           <height>96</height>\n          </size>\n         </property>\n         <property name=\"text\">\n          <string/>\n         </property>\n         <property name=\"pixmap\">\n          <pixmap resource=\"assets.qrc\">:/icon/assets/icon/fontedit96.png</pixmap>\n         </property>\n         <property name=\"scaledContents\">\n          <bool>true</bool>\n         </property>\n        </widget>\n       </item>\n       <item>\n        <spacer name=\"verticalSpacer\">\n         <property name=\"orientation\">\n          <enum>Qt::Vertical</enum>\n         </property>\n         <property name=\"sizeHint\" stdset=\"0\">\n          <size>\n           <width>20</width>\n           <height>40</height>\n          </size>\n         </property>\n        </spacer>\n       </item>\n      </layout>\n     </item>\n     <item>\n      <layout class=\"QVBoxLayout\" name=\"verticalLayout_4\">\n       <item>\n        <widget class=\"QLabel\" name=\"htmlLabel\">\n         <property name=\"sizePolicy\">\n          <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n           <horstretch>0</horstretch>\n           <verstretch>0</verstretch>\n          </sizepolicy>\n         </property>\n         <property name=\"text\">\n          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:18pt;&quot;&gt;FontEdit &lt;/span&gt;&lt;span style=&quot; font-size:13pt; font-weight:400;&quot;&gt;v##version## build ##build##&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-size:13pt; font-weight:400;&quot;&gt;Copyright ##year## Dominik Kapusta&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://github.com/ayoy/fontedit&quot;&gt;&lt;span style=&quot; font-size:13pt; font-weight:400; text-decoration: underline; color:#0068da;&quot;&gt;Get Source Code&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:13pt; font-weight:400;&quot;&gt;•&lt;/span&gt;&lt;a href=&quot;https://github.com/ayoy/fontedit/issues&quot;&gt;&lt;span style=&quot; font-size:13pt; font-weight:400; text-decoration: underline; color:#0068da;&quot;&gt;Report a Bug&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-size:13pt; font-weight:400;&quot;&gt;This program is distributed under the terms of &lt;/span&gt;&lt;a href=&quot;https://www.gnu.org/licenses/gpl-3.0.en.html&quot;&gt;&lt;span style=&quot; font-size:13pt; font-weight:400; text-decoration: underline; color:#0068da;&quot;&gt;General Public License v3&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:13pt;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-size:13pt; font-weight:400;&quot;&gt;&lt;br/&gt;Icons made by &lt;/span&gt;&lt;a href=&quot;https://www.flaticon.com/authors/smashicons&quot;&gt;&lt;span style=&quot; font-size:13pt; font-weight:400; text-decoration: underline; color:#0068da;&quot;&gt;Smashicons&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:13pt; font-weight:400;&quot;&gt; and &lt;/span&gt;&lt;a href=&quot;https://www.flaticon.com/authors/freepik&quot;&gt;&lt;span style=&quot; font-size:13pt; font-weight:400; text-decoration: underline; color:#0068da;&quot;&gt;Freepik&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:13pt; font-weight:400;&quot;&gt; from &lt;/span&gt;&lt;a href=&quot;https://www.flaticon.com/&quot;&gt;&lt;span style=&quot; font-size:13pt; font-weight:400; text-decoration: underline; color:#0068da;&quot;&gt;www.flaticon.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:13pt; font-weight:400;&quot;&gt;.&lt;/span&gt;&lt;a href=&quot;https://www.flaticon.com/&quot;&gt;&lt;span style=&quot; font-size:13pt; font-weight:400; text-decoration: underline; color:#0068da;&quot;&gt;&lt;br/&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n         </property>\n         <property name=\"textFormat\">\n          <enum>Qt::RichText</enum>\n         </property>\n         <property name=\"scaledContents\">\n          <bool>false</bool>\n         </property>\n         <property name=\"alignment\">\n          <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>\n         </property>\n         <property name=\"wordWrap\">\n          <bool>true</bool>\n         </property>\n        </widget>\n       </item>\n       <item>\n        <spacer name=\"verticalSpacer_2\">\n         <property name=\"orientation\">\n          <enum>Qt::Vertical</enum>\n         </property>\n         <property name=\"sizeHint\" stdset=\"0\">\n          <size>\n           <width>20</width>\n           <height>40</height>\n          </size>\n         </property>\n        </spacer>\n       </item>\n      </layout>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <widget class=\"QDialogButtonBox\" name=\"buttonBox\">\n     <property name=\"orientation\">\n      <enum>Qt::Horizontal</enum>\n     </property>\n     <property name=\"standardButtons\">\n      <set>QDialogButtonBox::Ok</set>\n     </property>\n     <property name=\"centerButtons\">\n      <bool>true</bool>\n     </property>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <resources>\n  <include location=\"assets.qrc\"/>\n </resources>\n <connections>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>accepted()</signal>\n   <receiver>AboutDialog</receiver>\n   <slot>accept()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>248</x>\n     <y>254</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>157</x>\n     <y>274</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>rejected()</signal>\n   <receiver>AboutDialog</receiver>\n   <slot>reject()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>316</x>\n     <y>260</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>286</x>\n     <y>274</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n</ui>\n"
  },
  {
    "path": "app/ui/batchpixelchange.h",
    "content": "#ifndef GLYPHEDITCOMMAND_H\n#define GLYPHEDITCOMMAND_H\n\n#include <unordered_map>\n#include <memory>\n#include <functional>\n#include <f2b.h>\n\nstruct PointHash {\n    size_t operator()(const f2b::font::point &p) const {\n        return std::hash<std::size_t>()(p.x) ^ std::hash<std::size_t>()(p.y);\n    }\n};\n\nstruct BatchPixelChange {\n    enum class ChangeType {\n        Normal, Reverse\n    };\n\n    std::unordered_map<f2b::font::point,bool,PointHash> changes;\n\n    void add(const f2b::font::point &pixel, bool value) {\n        auto i = changes.find(pixel);\n        if (i == changes.end()) {\n            changes.insert({ pixel, value });\n        } else if (i->second != value) {\n            changes.erase(pixel);\n        }\n    }\n\n    void apply(f2b::font::glyph& glyph, ChangeType type = ChangeType::Normal) const {\n        for (const auto& i : changes) {\n            glyph.set_pixel_set(i.first, type == ChangeType::Normal ? i.second : !i.second);\n        }\n    }\n};\n\n\n#endif // GLYPHEDITCOMMAND_H\n"
  },
  {
    "path": "app/ui/facewidget.cpp",
    "content": "#include \"facewidget.h\"\n#include \"glyphinfowidget.h\"\n#include \"f2b_qt_compat.h\"\n\n#include <QGraphicsSceneEvent>\n#include <QGraphicsScene>\n#include <QGraphicsView>\n#include <QQueue>\n#include <algorithm>\n#include <iterator>\n\nstatic constexpr auto printable_ascii_offset = ' ';\nstatic constexpr auto min_cell_height = 120.0;\nstatic constexpr auto min_image_height = min_cell_height - GlyphInfoWidget::descriptionHeight - 3 * GlyphInfoWidget::cellMargin;\nstatic constexpr auto max_image_width = FaceWidget::cell_width - 2 * GlyphInfoWidget::cellMargin;\n\nFaceWidget::FaceWidget(int columnCount, QGraphicsItem *parent) :\n    QGraphicsWidget(parent),\n    columnCount_ { columnCount }\n{\n    setFocusPolicy(Qt::ClickFocus);\n    layout_->setSpacing(0);\n    layout_->setContentsMargins(0, 0, 0, 0);\n    setLayout(layout_);\n}\n\nvoid FaceWidget::reset()\n{\n    // TODO: Reuse items instead of deleting them all\n    for (auto& item : childItems()) {\n        if (item->zValue() == 0) {\n            delete item;\n        }\n    }\n\n    resetFocusWidget();\n}\n\nQSizeF FaceWidget::calculateImageSize(f2b::font::glyph_size glyph_size)\n{\n    // height: 6 + desc.height + 6 + img.height + 6\n    // width: 6 + img.width + 6\n    // max image width: 80 - 2*6 = 68\n\n    QSizeF imageSize { f2b::font::qsize_with_size(glyph_size) };\n    if (imageSize.width() > max_image_width) {\n        imageSize.scale(max_image_width, qInf(), Qt::KeepAspectRatio);\n    } else if (imageSize.height() < min_image_height) {\n        imageSize.scale(qInf(), min_image_height, Qt::KeepAspectRatio);\n    }\n    auto size = imageSize;\n    size.rheight() += GlyphInfoWidget::cellMargin + GlyphInfoWidget::descriptionHeight;\n    size.rwidth() += 2 * GlyphInfoWidget::cellMargin;\n    size.rheight() += 2 * GlyphInfoWidget::cellMargin;\n    size.rheight() = qMax(size.height(), min_cell_height);\n    size.rwidth() = qMax(size.width(), cell_width);\n\n    itemSize_ = size;\n    qDebug() << \"NEW ITEM SIZE:\" << itemSize_;\n\n    return imageSize;\n}\n\nvoid FaceWidget::load(const f2b::font::face &face, f2b::font::margins margins)\n{\n    face_ = &face;\n    margins_ = margins;\n    reset();\n    auto imageSize = calculateImageSize(face.glyphs_size());\n\n    auto index = 0;\n    for (const auto& g : face.glyphs()) {\n        auto glyphWidget = new GlyphInfoWidget(g, index, true, printable_ascii_offset + index, imageSize, margins);\n        glyphWidget->setIsExportedAdjustable(false);\n\n        addGlyphInfoWidget(glyphWidget, index);\n        index++;\n    }\n}\n\nvoid FaceWidget::load(f2b::font::face &face, f2b::font::margins margins)\n{\n    face_ = &face;\n    margins_ = margins;\n    reloadFace();\n}\n\nvoid FaceWidget::reloadFace()\n{\n    reset();\n\n    if (face_ == nullptr) {\n        return;\n    }\n\n    auto imageSize = calculateImageSize(face_->glyphs_size());\n\n    auto index = 0;\n    auto widgetIndex = 0;\n    auto exportedGlyphIDs = face_->exported_glyph_ids();\n    for (const auto& g : face_->glyphs()) {\n        auto isExported = exportedGlyphIDs.find(index) != exportedGlyphIDs.end();\n\n        if (isExported || showsNonExportedItems_) {\n            auto glyphWidget = new GlyphInfoWidget(g, index, isExported, printable_ascii_offset + index, imageSize, margins_);\n\n            connect(glyphWidget, &GlyphInfoWidget::isExportedChanged, [&, index] (bool isExported) {\n                emit glyphExportedStateChanged(index, isExported);\n            });\n\n            addGlyphInfoWidget(glyphWidget, widgetIndex);\n            widgetIndex++;\n        }\n\n        index++;\n    }\n}\n\nvoid FaceWidget::addGlyphInfoWidget(QGraphicsLayoutItem *glyphWidget, std::size_t index)\n{\n    auto row = index / columnCount_;\n    auto col = index % columnCount_;\n\n    if (row == 0) {\n        layout_->setColumnFixedWidth(index % columnCount_, itemSize_.width());\n    }\n\n    if (col == 0) {\n        layout_->setRowFixedHeight(index / columnCount_, itemSize_.height());\n    }\n\n    layout_->addItem(glyphWidget, index / columnCount_, index % columnCount_, 1, 1);\n}\n\nvoid FaceWidget::setCurrentGlyphIndex(std::optional<std::size_t> index)\n{\n    if (index.has_value()) {\n        auto item = glyphWidgetAtIndex(index.value());\n        if (item) {\n            setFocusForItem(item, true);\n        }\n    } else {\n        clearFocus();\n    }\n}\n\nvoid FaceWidget::updateGlyphInfo(std::size_t index, std::optional<f2b::font::glyph> glyph, std::optional<bool> isExported)\n{\n    auto item = glyphWidgetAtIndex(index);\n    if (item) {\n        item->updateGlyph(glyph, isExported);\n    }\n}\n\nGlyphInfoWidget* FaceWidget::glyphWidgetAtIndex(std::size_t index)\n{\n    int itemIndex = index;\n\n    if (!showsNonExportedItems_) {\n        itemIndex = std::count_if(face_->exported_glyph_ids().begin(),\n                                  face_->exported_glyph_ids().find(index),\n                                  [&](std::size_t i) { return i < index; });\n    }\n\n    return dynamic_cast<GlyphInfoWidget *>(layout_->itemAt(itemIndex / columnCount_,\n                                                           itemIndex % columnCount_));\n}\n\nQSize FaceWidget::glyphCoordsAtPos(QPointF pos)\n{\n    qreal leftMargin;\n    qreal topMargin;\n    layout_->getContentsMargins(&leftMargin, &topMargin, nullptr, nullptr);\n    int row = static_cast<int>((pos.y() - topMargin) / itemSize_.height());\n    int col = static_cast<int>((pos.x() - leftMargin) / itemSize_.width());\n    return { col, row };\n}\n\nGlyphInfoWidget* FaceWidget::glyphWidgetAtPos(QPointF pos)\n{\n    auto coords = glyphCoordsAtPos(pos);\n    return dynamic_cast<GlyphInfoWidget *>(layout_->itemAt(coords.height(), coords.width()));\n}\n\nvoid FaceWidget::setFocusForItem(QGraphicsLayoutItem *item, bool isFocused)\n{\n    if (focusWidget_ == nullptr) {\n        focusWidget_ = std::make_unique<FocusWidget>(this);\n        focusWidget_->setZValue(1);\n        focusWidget_->setColor(Qt::blue);\n    }\n\n    focusedItem_ = item;\n    focusWidget_->setFocus(item, isFocused);\n    if (isFocused) {\n        QGraphicsView *graphicsView = scene()->views().first();\n        if (graphicsView != nullptr)\n            graphicsView->ensureVisible(focusWidget_->geometry());\n    }\n}\n\nvoid FaceWidget::resetFocusWidget()\n{\n    if (focusWidget_ != nullptr) {\n        focusWidget_->setFocus(nullptr);\n    }\n    focusedItem_ = nullptr;\n}\n\nvoid FaceWidget::keyPressEvent(QKeyEvent *event)\n{\n    if (focusWidget_ == nullptr) {\n        return;\n    }\n\n    auto coords = glyphCoordsAtPos(focusWidget_->pos());\n    auto item = dynamic_cast<GlyphInfoWidget *>(layout_->itemAt(coords.height(), coords.width()));\n    if (item == nullptr) {\n        return;\n    }\n\n    switch (event->key()) {\n    case Qt::Key_Left:\n    case Qt::Key_H:\n        if (coords.width() > 0)\n            --coords.rwidth();\n        break;\n    case Qt::Key_Right:\n    case Qt::Key_L:\n        if (coords.width() < columnCount_ - 1)\n            ++coords.rwidth();\n        break;\n    case Qt::Key_Up:\n    case Qt::Key_K:\n        if (coords.height() > 0)\n            --coords.rheight();\n        break;\n    case Qt::Key_Down:\n    case Qt::Key_J: {\n        auto rowCount = (layout_->count() + columnCount_) / columnCount_;\n        if (coords.height() < rowCount - 1)\n            ++coords.rheight();\n        break;\n    }\n    case Qt::Key_Space: {\n        auto isExported = face_->exported_glyph_ids().find(item->glyphIndex()) != face_->exported_glyph_ids().end();\n        emit glyphExportedStateChanged(item->glyphIndex(), !isExported);\n    }\n    }\n\n    item = dynamic_cast<GlyphInfoWidget *>(layout_->itemAt(coords.height(), coords.width()));\n    if (item) {\n        setFocusForItem(item, true);\n        emit currentGlyphIndexChanged(item->glyphIndex());\n    }\n}\n\nvoid FaceWidget::mousePressEvent(QGraphicsSceneMouseEvent *event)\n{\n    handleMousePress(event);\n}\n\nvoid FaceWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)\n{\n    handleMousePress(event);\n}\n\nvoid FaceWidget::handleMousePress(QGraphicsSceneMouseEvent *event)\n{\n    auto item = glyphWidgetAtPos(event->pos());\n    if (item != nullptr) {\n        setFocusForItem(item, true);\n        emit currentGlyphIndexChanged(item->glyphIndex());\n    } else {\n        resetFocusWidget();\n    }\n}\n\nvoid FaceWidget::updateGeometry()\n{\n    QGraphicsWidget::updateGeometry();\n    if (auto fw = focusWidget_.get()) {\n        fw->setFocus(focusedItem_, true);\n    }\n}\n\nvoid FaceWidget::setShowsNonExportedItems(bool isEnabled)\n{\n    if (showsNonExportedItems_ != isEnabled) {\n        showsNonExportedItems_ = isEnabled;\n        if (face_ != nullptr && face_->exported_glyph_ids().size() != face_->num_glyphs()) {\n            reloadFace();\n        }\n        if (auto fw = focusWidget_.get()) {\n            auto item = glyphWidgetAtPos(fw->pos());\n            if (item != nullptr) {\n                setFocusForItem(item, true);\n                emit currentGlyphIndexChanged(item->glyphIndex());\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/ui/facewidget.h",
    "content": "#ifndef FACEWIDGET_H\n#define FACEWIDGET_H\n\n#include <QGraphicsWidget>\n#include <QGraphicsGridLayout>\n#include <f2b.h>\n#include \"focuswidget.h\"\n\n#include <memory>\n#include <optional>\n\nclass GlyphInfoWidget;\n\nclass FaceWidget : public QGraphicsWidget\n{\n    Q_OBJECT\n\npublic:\n    static constexpr auto cell_width = 80.0;\n\n    explicit FaceWidget(int columnCount = 3, QGraphicsItem *parent = nullptr);\n\n    void load(const f2b::font::face& face, f2b::font::margins margins);\n    void load(f2b::font::face& face, f2b::font::margins margins);\n    void setCurrentGlyphIndex(std::optional<std::size_t> index);\n    void updateGlyphInfo(std::size_t index, std::optional<f2b::font::glyph> glyph, std::optional<bool> isExported = {});\n\n    bool showsNonExportedItems() const { return showsNonExportedItems_; }\n    void setShowsNonExportedItems(bool isEnabled);\n\nsignals:\n    void currentGlyphIndexChanged(std::optional<std::size_t> index);\n    void glyphExportedStateChanged(std::size_t index, bool isExported);\n\nprotected:\n    void mousePressEvent(QGraphicsSceneMouseEvent *event) override;\n    void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;\n    void keyPressEvent(QKeyEvent *event) override;\n    void updateGeometry() override;\n\nprivate:\n    void handleMousePress(QGraphicsSceneMouseEvent *event);\n    void reloadFace();\n    void reset();\n    QSizeF calculateImageSize(f2b::font::glyph_size glyph_size);\n    void addGlyphInfoWidget(QGraphicsLayoutItem* glyphWidget, std::size_t index);\n    void setFocusForItem(QGraphicsLayoutItem *item, bool isFocused);\n    void resetFocusWidget();\n\n\n    GlyphInfoWidget* glyphWidgetAtIndex(std::size_t index);\n    GlyphInfoWidget* glyphWidgetAtPos(QPointF pos);\n    QSize glyphCoordsAtPos(QPointF pos);\n\n    QGraphicsLayoutItem *focusedItem_ { nullptr };\n    QGraphicsGridLayout *layout_ { new QGraphicsGridLayout() };\n    std::unique_ptr<FocusWidget> focusWidget_ { nullptr };\n    QSizeF itemSize_;\n    int columnCount_;\n    bool showsNonExportedItems_;\n    const f2b::font::face* face_ { nullptr };\n    f2b::font::margins margins_;\n};\n\n#endif // FACEWIDGET_H\n"
  },
  {
    "path": "app/ui/focuswidget.cpp",
    "content": "#include \"focuswidget.h\"\n#include <QPainter>\n#include <QDebug>\n#include <QGraphicsScene>\n#include <QGraphicsView>\n\nFocusWidget::FocusWidget(QGraphicsItem *parent)\n    : QGraphicsWidget(parent)\n{\n    setZValue(1);\n}\n\nvoid FocusWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)\n{\n    Q_UNUSED(option);\n    Q_UNUSED(widget);\n    painter->setPen(QPen(QBrush(color_), 2));\n    painter->drawRect(rect());\n}\n\nvoid FocusWidget::setFocus(QGraphicsLayoutItem *item, bool isFocused)\n{\n    if (item != nullptr) {\n        if (isFocused) {\n            setVisible(true);\n            setGeometry(item->geometry());\n            ensureVisible();\n        } else {\n            setVisible(false);\n        }\n    } else {\n        setVisible(false);\n    }\n}\n\nvoid FocusWidget::ensureVisible()\n{\n    QGraphicsView *graphicsView = scene()->views().first();\n    if (graphicsView != nullptr)\n        graphicsView->ensureVisible(this);\n}\n"
  },
  {
    "path": "app/ui/focuswidget.h",
    "content": "#ifndef FOCUSWIDGET_H\n#define FOCUSWIDGET_H\n\n#include <QGraphicsWidget>\n\nclass QGraphicsLayoutItem;\n\nclass FocusWidget : public QGraphicsWidget\n{\npublic:\n    explicit FocusWidget(QGraphicsItem *parent = nullptr);\n    virtual ~FocusWidget() = default;\n\n    void setFocus(QGraphicsLayoutItem *item, bool isFocused = true);\n\n    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;\n\n    const QColor& color() const { return color_; }\n    void setColor(const QColor &color) { color_ = color; update(); }\n\nprivate:\n    void ensureVisible();\n    QColor color_ { Qt::red };\n};\n\n#endif // FOCUSWIDGET_H\n"
  },
  {
    "path": "app/ui/glyphgraphicsview.cpp",
    "content": "#include \"glyphgraphicsview.h\"\n#include <QTouchEvent>\n#include <QDebug>\n#include <QTimeLine>\n#include <cmath>\n#include \"glyphwidget.h\"\n#include <iostream>\n\nstatic constexpr auto max_zoom_level = 2.0;\nstatic constexpr auto min_zoom_level = 0.1;\nstatic constexpr auto zoom_factor = 1.01;\n\nGlyphGraphicsView::GlyphGraphicsView(QWidget *parent) :\n    QGraphicsView(parent),\n    scene_ { std::make_unique<QGraphicsScene>() }\n{\n    scene_->setBackgroundBrush(QBrush(Qt::lightGray));\n    setScene(scene_.get());\n}\n\nvoid GlyphGraphicsView::resizeEvent(QResizeEvent *event)\n{\n    Q_UNUSED(event);\n    if (!scene()->items().isEmpty()) {\n        auto glyphView = scene()->items().constLast();\n        fitInView(glyphView, Qt::KeepAspectRatio);\n    }\n}\n\nvoid GlyphGraphicsView::wheelEvent(QWheelEvent *event)\n{\n    auto isZooming = event->modifiers().testFlag(Qt::ControlModifier);\n\n    if (isZooming && !event->angleDelta().isNull()) {\n        qreal factor = pow(zoom_factor, event->angleDelta().y());\n        setScale(factor);\n    } else {\n        QGraphicsView::wheelEvent(event);\n    }\n}\n\nvoid GlyphGraphicsView::setScale(qreal factor)\n{\n    auto transform = this->transform();\n\n    bool isZoomingInBeyondLimit { factor > 1.0 && transform.m11() > max_zoom_level };\n    bool isZoomingOutBeyondLimit { factor < 1.0 && transform.m11() < min_zoom_level };\n\n    if (isZoomingInBeyondLimit || isZoomingOutBeyondLimit) {\n        return;\n    }\n\n    auto targetTransform = transform.scale(factor, factor);\n\n    if (targetTransform.m11() > max_zoom_level) {\n        auto clippedFactor = max_zoom_level / transform.m11();\n        targetTransform = transform.scale(clippedFactor, clippedFactor);\n    } else if (targetTransform.m11() < min_zoom_level) {\n        auto clippedFactor = min_zoom_level / transform.m11();\n        targetTransform = transform.scale(clippedFactor, clippedFactor);\n    }\n\n    setTransform(targetTransform);\n}\n"
  },
  {
    "path": "app/ui/glyphgraphicsview.h",
    "content": "#ifndef GLYPHGRAPHICSVIEW_H\n#define GLYPHGRAPHICSVIEW_H\n\n#include <QGraphicsView>\n#include <optional>\n#include \"f2b.h\"\n\nclass GlyphWidget;\n\nclass GlyphGraphicsView : public QGraphicsView\n{\npublic:\n    explicit GlyphGraphicsView(QWidget *parent = nullptr);\n\nprotected:\n    void wheelEvent(QWheelEvent *event) override;\n    void resizeEvent(QResizeEvent *event) override;\n\nprivate:\n    void setScale(qreal factor);\n\n    std::unique_ptr<QGraphicsScene> scene_;\n};\n\n#endif // GLYPHGRAPHICSVIEW_H\n"
  },
  {
    "path": "app/ui/glyphinfowidget.cpp",
    "content": "#include \"glyphinfowidget.h\"\n#include \"f2b_qt_compat.h\"\n#include \"common.h\"\n\n#include <QPainter>\n#include <QDebug>\n#include <QMenu>\n#include <QGraphicsSceneContextMenuEvent>\n\n#include <sstream>\n#include <iomanip>\n\nstatic QString description(unsigned char asciiCode)\n{\n    std::stringstream stream;\n    stream << \"hex: 0x\" << std::setw(2) << std::setfill('0') << std::hex << static_cast<uint>(asciiCode) << std::endl;\n    stream << \"dec: \" << std::setw(3) << std::dec << static_cast<uint>(asciiCode) << std::endl;\n    if (std::isprint(asciiCode)) {\n        stream << \"chr: '\" << asciiCode << \"'\";\n    }\n    return QString::fromStdString(stream.str());\n}\n\nGlyphInfoWidget::GlyphInfoWidget(const f2b::font::glyph &glyph, std::size_t index, bool isExported,\n                                 unsigned char asciiCode, QSizeF imageSize,\n                                 f2b::font::margins margins, QGraphicsItem *parent) :\n    QGraphicsWidget(parent),\n    description_ { description(asciiCode) },\n    imageSize_ { imageSize },\n    isExportedAdjustable_ { true },\n    isExported_ { isExported },\n    preview_ { f2b::font::glyph_preview_image(glyph, margins) },\n    margins_ { margins },\n    toggleExportedAction_ { QAction(tr(\"Exported\")) },\n    glyphIndex_ { index }\n{\n    toggleExportedAction_.setCheckable(true);\n    toggleExportedAction_.setChecked(isExported_);\n\n    connect(&toggleExportedAction_, &QAction::triggered, this, &GlyphInfoWidget::isExportedChanged);\n}\n\nvoid GlyphInfoWidget::setIsExportedAdjustable(bool isEnabled)\n{\n    isExportedAdjustable_ = isEnabled;\n}\n\nvoid GlyphInfoWidget::updateGlyph(std::optional<f2b::font::glyph> glyph, std::optional<bool> isExported, std::optional<f2b::font::margins> margins)\n{\n    if (isExported.has_value()) {\n        isExported_ = isExported.value();\n    }\n    if (margins.has_value()) {\n        margins_ = margins.value();\n    }\n    if (glyph.has_value()) {\n        preview_ = f2b::font::glyph_preview_image(glyph.value(), margins_);\n    }\n    update();\n}\n\nvoid GlyphInfoWidget::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)\n{\n    if (!isExportedAdjustable_) {\n        return;\n    }\n    QMenu menu;\n    menu.addAction(&toggleExportedAction_);\n\n    menu.exec(event->screenPos());\n}\n\nvoid GlyphInfoWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)\n{\n    Q_UNUSED(option);\n    Q_UNUSED(widget);    \n\n    painter->fillRect(rect(), QBrush(Qt::white));\n    QPen pen(QBrush(Qt::darkGray), 0.5);\n    painter->setPen(pen);\n    painter->drawRect(rect());\n\n    QFont f(consoleFontName);\n    f.setStyleHint(QFont::TypeWriter);\n    f.setPixelSize(12);\n    QRectF textRect(rect());\n    textRect.setTop(cellMargin);\n    textRect.setLeft(cellMargin);\n    textRect.setWidth(textRect.width() - cellMargin);\n    textRect.setHeight(descriptionHeight);\n\n    if (!isExported_) {\n        painter->setPen(QPen(QBrush(QColor(Color::inactiveText)), 0.5));\n    }\n\n    painter->setFont(f);\n    painter->drawText(textRect, Qt::TextWordWrap, description_);\n\n\n    // Preview rect (with margins)\n\n    QRectF imageRect { QPointF(), imageSize_ };\n    imageRect.moveCenter(rect().center().toPoint());\n    imageRect.moveBottom(rect().height() - cellMargin);\n    painter->fillRect(imageRect, Color::glyphMargin);\n\n\n    // Glyph rect (margins removed)\n    preview_.setColor(0, isExported_ ? Color::activeGlyph : Color::inactiveGlyph);\n\n    QRectF imageSansMarginsRect = imageRect.marginsRemoved(QMarginsF(0, margins_.top, 0, margins_.bottom));\n    painter->drawImage(imageSansMarginsRect, preview_, preview_.rect());\n\n\n    // Glyph preview outline\n\n    painter->setPen(QPen(QBrush(Qt::lightGray), 1));\n    painter->drawRect(imageRect.marginsAdded(QMarginsF(0.5, 0.5, 0.5, 0.5)));\n}\n"
  },
  {
    "path": "app/ui/glyphinfowidget.h",
    "content": "#ifndef GLYPHINFOWIDGET_H\n#define GLYPHINFOWIDGET_H\n\n#include <QGraphicsWidget>\n#include <QImage>\n#include <QAction>\n#include <f2b.h>\n\n#include <optional>\n\nclass GlyphInfoWidget : public QGraphicsWidget\n{\n    Q_OBJECT\n\npublic:\n    static constexpr auto cellMargin = 6.0;\n    static constexpr auto descriptionHeight = 50.0;\n\n    GlyphInfoWidget(const f2b::font::glyph& glyph, std::size_t index, bool isExported,\n                    unsigned char asciiCode, QSizeF imageSize,\n                    f2b::font::margins margins = {}, QGraphicsItem *parent = nullptr);\n\n    std::size_t glyphIndex() const { return glyphIndex_; }\n    void updateGlyph(std::optional<f2b::font::glyph> glyph, std::optional<bool> isExported = {}, std::optional<f2b::font::margins> margins = {});\n\n    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;\n\n    void setIsExportedAdjustable(bool isEnabled);\n\nsignals:\n    void isExportedChanged(bool isExported);\n\nprotected:\n    void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;\n\nprivate:\n    const QString description_;\n    const QSizeF imageSize_;\n    bool isExportedAdjustable_;\n    bool isExported_;\n    QImage preview_;\n    f2b::font::margins margins_;\n    QAction toggleExportedAction_;\n\n    std::size_t glyphIndex_;\n};\n\n#endif // GLYPHINFOWIDGET_H\n"
  },
  {
    "path": "app/ui/glyphwidget.cpp",
    "content": "#include \"glyphwidget.h\"\n#include \"common.h\"\n#include <QPainter>\n#include <QDebug>\n#include <QGraphicsSceneMouseEvent>\n#include <QKeyEvent>\n#include <QStyleOptionGraphicsItem>\n#include <QElapsedTimer>\n\n#include <iostream>\n\nstatic constexpr qreal gridSize = 20;\n\nQRectF rectForPoint(const f2b::font::point& point)\n{\n    return QRectF(QPointF(point.x * gridSize, point.y * gridSize),\n                  QSizeF(gridSize, gridSize));\n}\n\n\nGlyphWidget::GlyphWidget(const f2b::font::glyph& glyph, f2b::font::margins margins, QGraphicsItem* parent) :\n    QGraphicsWidget(parent),\n    glyph_ { glyph },\n    margins_ { margins }\n{\n    setFocusPolicy(Qt::ClickFocus);\n    setPreferredSize({ gridSize * static_cast<qreal>(glyph.size().width),\n                       gridSize * static_cast<qreal>(glyph.size().height) });\n}\n\nvoid GlyphWidget::load(const f2b::font::glyph &glyph, f2b::font::margins margins)\n{\n    glyph_ = glyph;\n    margins_ = margins;\n    setPreferredSize({ gridSize * static_cast<qreal>(glyph.size().width),\n                       gridSize * static_cast<qreal>(glyph.size().height) });\n    update();\n}\n\nQRectF GlyphWidget::boundingRect() const\n{\n    return QRectF(-0.25,\n                  -0.25,\n                  glyph_.size().width * gridSize + 0.25,\n                  glyph_.size().height * gridSize + 0.25);\n}\n\nvoid GlyphWidget::togglePixel(f2b::font::point p)\n{\n    setPixel(p, !glyph_.is_pixel_set(p));\n}\n\nvoid GlyphWidget::setPixel(f2b::font::point p, bool value)\n{\n    if (glyph_.is_pixel_set(p) != value) {\n        glyph_.set_pixel_set(p, value);\n        affectedPixels_.add(p, value);\n\n        if (!isDuringMouseMove_) {\n            emit pixelsChanged(affectedPixels_);\n            affectedPixels_.changes.clear();\n        }\n    }\n}\n\nvoid GlyphWidget::applyChange(const BatchPixelChange &change, BatchPixelChange::ChangeType changeType)\n{\n    //\n    // Apply only if there are no affected pixels (no operation in progress)\n    // - this is the initial call to redo() action.\n    //\n    if (affectedPixels_.changes.size() == 0) {\n        change.apply(glyph_, changeType);\n        update();\n    }\n}\n\n\nvoid GlyphWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)\n{\n    Q_UNUSED(option);\n    Q_UNUSED(widget);\n\n//    QElapsedTimer timer;\n//    timer.start();\n\n    QRectF rect = QRectF(0, 0,\n                         glyph_.size().width * gridSize,\n                         glyph_.size().height * gridSize);\n    QRectF activeAreaRect = QRectF(0, margins_.top * gridSize,\n                                   boundingRect().width(),\n                                   (glyph_.size().height - margins_.top - margins_.bottom) * gridSize);\n\n    painter->fillRect(rect, Color::glyphMargin);\n    painter->fillRect(activeAreaRect, Qt::white);\n\n    painter->setPen(QPen(QBrush(Qt::darkGray), 0.5));\n    painter->drawRect(rect);\n\n    for (std::size_t row = 0; row < glyph_.size().height; ++row) {\n        for (std::size_t col = 0; col < glyph_.size().width; ++col) {\n            f2b::font::point p { col, row };\n            if (glyph_.is_pixel_set(p)) {\n                painter->fillRect(rectForPoint(p), Qt::black);\n            }\n        }\n    }\n\n    for (qreal w = 0; w < rect.width(); w += gridSize) {\n        painter->drawLine(QLineF(QPointF(w, rect.top()), QPointF(w, rect.bottom())));\n    }\n    for (qreal h = 0; h < rect.height(); h += gridSize) {\n        painter->drawLine(QLineF(QPointF(rect.left(), h), QPointF(rect.right(), h)));\n    }\n    if (focusedPixel_.has_value()) {\n        painter->setPen(QPen(QBrush(Qt::red), 1));\n        painter->drawRect(rectForPoint(focusedPixel_.value()));\n    }\n\n//    qDebug() << __FUNCTION__ << option->exposedRect << boundingRect()\n//             << option->exposedRect.toRect().contains(boundingRect().toRect())\n//             << QString(\"(%1ms)\").arg(timer.elapsed());\n}\n\nvoid GlyphWidget::keyPressEvent(QKeyEvent *event)\n{\n    if (!focusedPixel_.has_value()) {\n        return;\n    }\n\n    auto previousPixel = focusedPixel_;\n    auto updateMode = UpdateMode::UpdateFocus;\n\n    switch (event->key()) {\n    case Qt::Key_Left:\n    case Qt::Key_H:\n        if (focusedPixel_->x > 0)\n            --focusedPixel_->x;\n        break;\n    case Qt::Key_Right:\n    case Qt::Key_L:\n        if (focusedPixel_->x < glyph_.size().width - 1)\n            ++focusedPixel_->x;\n        break;\n    case Qt::Key_Up:\n    case Qt::Key_K:\n        if (focusedPixel_->y > 0)\n            --focusedPixel_->y;\n        break;\n    case Qt::Key_Down:\n    case Qt::Key_J:\n        if (focusedPixel_->y < glyph_.size().height - 1)\n            ++focusedPixel_->y;\n        break;\n    case Qt::Key_Space:\n        togglePixel(focusedPixel_.value());\n        updateMode = UpdateMode::UpdateFocusAndPixels;\n        break;\n    case Qt::Key_Alt:\n    case Qt::Key_AltGr:\n    case Qt::Key_Control:\n        penState_ = false;\n        break;\n    }\n\n    updateIfNeeded(updateMode, previousPixel);\n}\n\nvoid GlyphWidget::keyReleaseEvent(QKeyEvent *event)\n{\n    if (!isDuringMouseMove_) {\n        return;\n    }\n\n    if (event->key() == Qt::Key_Alt\n            || event->key() == Qt::Key_AltGr\n            || event->key() == Qt::Key_Control)\n    {\n        penState_ = true;\n    }\n}\n\nvoid GlyphWidget::mousePressEvent(QGraphicsSceneMouseEvent *event)\n{\n    handleMousePress(event);\n}\n\nvoid GlyphWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)\n{\n    handleMousePress(event);\n}\n\nvoid GlyphWidget::handleMousePress(QGraphicsSceneMouseEvent *event)\n{\n    f2b::font::point currentPixel = pointForEvent(event);\n//    qDebug() << event << currentPixel.x << currentPixel.y;\n\n    penState_ = !event->modifiers().testFlag(Qt::AltModifier)\n            && !event->modifiers().testFlag(Qt::ControlModifier);\n    affectedPixels_.changes.clear();\n    isDuringMouseMove_ = true;\n\n    setPixel(currentPixel, penState_);\n\n    auto previousPixel = focusedPixel_;\n    focusedPixel_ = currentPixel;\n    updateIfNeeded(UpdateMode::UpdateFocusAndPixels, previousPixel);\n}\n\nvoid GlyphWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event)\n{\n    f2b::font::point currentPixel = pointForEvent(event);\n\n    auto updateMode = UpdateMode::UpdateFocus;\n\n    // If item not visited or visited with a different state\n    if (affectedPixels_.changes.find(currentPixel) == affectedPixels_.changes.end() ||\n            affectedPixels_.changes[currentPixel] != penState_)\n    {\n//        qDebug() << \"mouse move to new item\" << currentPixel.x << currentPixel.y << penState_;\n        updateMode = UpdateMode::UpdateFocusAndPixels;\n\n        setPixel(currentPixel, penState_);\n    }\n\n    auto previousPixel = focusedPixel_;\n    focusedPixel_ = currentPixel;\n    updateIfNeeded(updateMode, previousPixel);\n}\n\nvoid GlyphWidget::updateIfNeeded(UpdateMode updateMode, std::optional<f2b::font::point> previousFocusedPixel)\n{\n    QRectF rect;\n\n    if (focusedPixel_.has_value()\n            && previousFocusedPixel.has_value()\n            && *focusedPixel_ == *previousFocusedPixel\n            && updateMode == UpdateMode::UpdateFocus)\n    {\n        // no change in focus so no redraw\n        return;\n    }\n\n    if (focusedPixel_.has_value()) {\n        rect = rectForPoint(focusedPixel_.value());\n    }\n    if (previousFocusedPixel.has_value()) {\n        auto previousRect = rectForPoint(previousFocusedPixel.value());\n        if (rect.isValid()) {\n            rect = rect.united(previousRect);\n        } else {\n            rect = previousRect;\n        }\n    }\n\n    if (rect.isValid()) {\n        update(rect.marginsAdded({0.5, 0.5, 0.5, 0.5}));\n    }\n}\n\nvoid GlyphWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)\n{\n    Q_UNUSED(event);\n\n    isDuringMouseMove_ = false;\n\n    for (const auto& i : affectedPixels_.changes) {\n        std::cout << \"((\" << i.first.x << \", \" << i.first.y << \"), \" << i.second << \") \";\n    }\n    std::cout << std::endl;\n\n    emit pixelsChanged(affectedPixels_);\n    affectedPixels_.changes.clear();\n}\n\nf2b::font::point GlyphWidget::pointForEvent(QGraphicsSceneMouseEvent *event) const\n{\n    auto row = static_cast<std::size_t>(std::max(event->pos().y() / gridSize, 0.0));\n    auto col = static_cast<std::size_t>(std::max(event->pos().x() / gridSize, 0.0));\n    row = std::min(row, glyph_.size().height-1);\n    col = std::min(col, glyph_.size().width-1);\n\n    return { col, row };\n}\n"
  },
  {
    "path": "app/ui/glyphwidget.h",
    "content": "#ifndef RAWGLYPHWIDGET_H\n#define RAWGLYPHWIDGET_H\n\n#include <QGraphicsWidget>\n#include <f2b.h>\n#include <memory>\n#include <optional>\n\n#include \"batchpixelchange.h\"\n\n\nclass GlyphWidget : public QGraphicsWidget\n{\n    Q_OBJECT\n\n    enum class UpdateMode {\n        UpdateFocus,\n        UpdateFocusAndPixels\n    };\n\npublic:\n    explicit GlyphWidget(const f2b::font::glyph& glyph, f2b::font::margins margins = {}, QGraphicsItem* parent = nullptr);\n    virtual ~GlyphWidget() = default;\n\n    void load(const f2b::font::glyph& glyph, f2b::font::margins margins = {});\n    QRectF boundingRect() const override;\n\n    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;\n    void applyChange(const BatchPixelChange &change,\n                     BatchPixelChange::ChangeType changeType = BatchPixelChange::ChangeType::Normal);\n\nsignals:\n    void pixelsChanged(const BatchPixelChange& changes);\n\nprotected:\n    void keyPressEvent(QKeyEvent *event) override;\n    void keyReleaseEvent(QKeyEvent *event) override;\n    void mousePressEvent(QGraphicsSceneMouseEvent *event) override;\n    void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;\n    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;\n    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;\n\nprivate:\n    void handleMousePress(QGraphicsSceneMouseEvent *event);\n    void updateIfNeeded(UpdateMode updateMode, std::optional<f2b::font::point> previousFocusedPixel);\n\n    void togglePixel(f2b::font::point p);\n    void setPixel(f2b::font::point p, bool value);\n\n    f2b::font::point pointForEvent(QGraphicsSceneMouseEvent *event) const;\n\n    f2b::font::glyph glyph_;\n    f2b::font::margins margins_;\n    BatchPixelChange affectedPixels_;\n    std::optional<f2b::font::point> focusedPixel_ {};\n\n    bool isDuringMouseMove_ { false };\n    bool penState_ { false };\n};\n\n#endif // RAWGLYPHWIDGET_H\n"
  },
  {
    "path": "app/updatehelper.cpp",
    "content": "#include \"updatehelper.h\"\n#include <QtNetwork>\n#include <QJsonDocument>\n#include <QApplication>\n#include \"semver.hpp\"\n\nnamespace SettingsKey {\nstatic const QString checkForUpdatesAtStartup = \"main_window/check_for_updates_at_startup\";\n}\n\nUpdateHelper::UpdateHelper(QObject *parent) : QObject(parent),\n    manager_ { std::make_unique<QNetworkAccessManager>() }\n{\n    shouldCheckAtStartup_ = settings_.value(SettingsKey::checkForUpdatesAtStartup, true).toBool();\n    manager_->setStrictTransportSecurityEnabled(true);\n//    qDebug() << QSslSocket::supportsSsl() << QSslSocket::sslLibraryBuildVersionString() << QSslSocket::sslLibraryVersionString();\n    connect(manager_.get(), &QNetworkAccessManager::finished, this, &UpdateHelper::handleReply);\n}\n\nvoid UpdateHelper::setShouldCheckAtStartup(bool check)\n{\n    shouldCheckAtStartup_ = check;\n    settings_.setValue(SettingsKey::checkForUpdatesAtStartup, check);\n}\n\nvoid UpdateHelper::checkForUpdatesIfNeeded()\n{\n    if (shouldCheckAtStartup_) {\n        checkForUpdates(false);\n    }\n}\n\nvoid UpdateHelper::checkForUpdates(bool verbose)\n{\n    QNetworkRequest request(QUrl(\"https://api.github.com/repos/ayoy/fontedit/releases/latest\"));\n    request.setRawHeader(\"Accept\", \"application/vnd.github.v3+json\");\n    request.setAttribute(QNetworkRequest::User, verbose);\n    manager_->get(request);\n}\n\nvoid UpdateHelper::handleReply(QNetworkReply *reply)\n{\n    auto verbose = reply->request().attribute(QNetworkRequest::User).toBool();\n\n    if (reply->error() != QNetworkReply::NoError) {\n        qDebug() << \"Error fetching latest version:\" << reply->error();\n        reply->deleteLater();\n        return;\n    }\n\n    QJsonParseError error;\n    auto replyJSON = QJsonDocument::fromJson(reply->readAll(), &error);\n    if (error.error != QJsonParseError::NoError) {\n        qDebug() << \"Error parsing JSON:\" << error.errorString();\n        reply->deleteLater();\n        return;\n    }\n\n    auto latestVersionString = replyJSON.object().value(\"tag_name\").toString();\n\n    if (latestVersionString.isNull()) {\n        qDebug() << \"Latest version unknown\";\n        reply->deleteLater();\n        return;\n    } else if (latestVersionString.startsWith('v')) {\n        latestVersionString.remove(0, 1);\n    }\n\n    semver::version latestVersion { latestVersionString.toStdString() };\n    semver::version currentVersion { QApplication::applicationVersion().toStdString() };\n\n    if (latestVersion > currentVersion) {\n        auto releaseNotes = replyJSON.object().value(\"body\").toString();\n        auto urlString = replyJSON.object().value(\"html_url\").toString();\n        qDebug() << \"An update is available. Check out\" << urlString << \"for more info\";\n        emit updateAvailable({\n                                 QApplication::applicationVersion(),\n                                 latestVersionString,\n                                 releaseNotes,\n                                 QUrl(urlString)\n                             });\n    } else {\n        qDebug() << \"No update available\";\n        if (verbose)\n            emit updateNotAvailable();\n    }\n\n    reply->deleteLater();\n}\n"
  },
  {
    "path": "app/updatehelper.h",
    "content": "#ifndef UPDATEHELPER_H\n#define UPDATEHELPER_H\n\n#include <QObject>\n#include <QtNetwork>\n#include <memory>\n\nclass UpdateHelper : public QObject\n{\n    Q_OBJECT\npublic:\n\n    struct Update {\n        QString currentVersion;\n        QString latestVersion;\n        QString releaseNotes;\n        QUrl webpageURL;\n    };\n\n    explicit UpdateHelper(QObject *parent = nullptr);\n\n    void checkForUpdatesIfNeeded();\n    void checkForUpdates(bool verbose = false);\n\n    bool shouldCheckAtStartup() const { return shouldCheckAtStartup_; }\n    void setShouldCheckAtStartup(bool check);\n\nsignals:\n    void updateAvailable(const Update& update);\n    void updateNotAvailable();\n\nprivate:\n    void handleReply(QNetworkReply *reply);\n\n    std::unique_ptr<QNetworkAccessManager> manager_;\n    bool shouldCheckAtStartup_ { true };\n    bool isChecking_ { false };\n    QSettings settings_;\n};\n\n#endif // UPDATEHELPER_H\n"
  },
  {
    "path": "app/utf8/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.9)\n\nset(CMAKE_AUTOUIC OFF)\nset(CMAKE_AUTOMOC OFF)\nset(CMAKE_AUTORCC OFF)\n\nproject(utf8 LANGUAGES CXX)\n\nadd_library(utf8 INTERFACE)\n\ntarget_include_directories(utf8 SYSTEM INTERFACE\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)\n"
  },
  {
    "path": "app/utf8/utf8/checked.h",
    "content": "// Copyright 2006 Nemanja Trifunovic\n\n/*\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n\n#ifndef UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731\n#define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731\n\n#include \"core.h\"\n#include <stdexcept>\n\nnamespace utf8\n{\n    // Base for the exceptions that may be thrown from the library\n    class exception : public ::std::exception {\n    };\n\n    // Exceptions that may be thrown from the library functions.\n    class invalid_code_point : public exception {\n        uint32_t cp;\n    public:\n        invalid_code_point(uint32_t cp) : cp(cp) {}\n        virtual const char* what() const throw() { return \"Invalid code point\"; }\n        uint32_t code_point() const {return cp;}\n    };\n\n    class invalid_utf8 : public exception {\n        uint8_t u8;\n    public:\n        invalid_utf8 (uint8_t u) : u8(u) {}\n        virtual const char* what() const throw() { return \"Invalid UTF-8\"; }\n        uint8_t utf8_octet() const {return u8;}\n    };\n\n    class invalid_utf16 : public exception {\n        uint16_t u16;\n    public:\n        invalid_utf16 (uint16_t u) : u16(u) {}\n        virtual const char* what() const throw() { return \"Invalid UTF-16\"; }\n        uint16_t utf16_word() const {return u16;}\n    };\n\n    class not_enough_room : public exception {\n    public:\n        virtual const char* what() const throw() { return \"Not enough space\"; }\n    };\n\n    /// The library API - functions intended to be called by the users\n\n    template <typename octet_iterator>\n    octet_iterator append(uint32_t cp, octet_iterator result)\n    {\n        if (!utf8::internal::is_code_point_valid(cp))\n            throw invalid_code_point(cp);\n\n        if (cp < 0x80)                        // one octet\n            *(result++) = static_cast<uint8_t>(cp);\n        else if (cp < 0x800) {                // two octets\n            *(result++) = static_cast<uint8_t>((cp >> 6)            | 0xc0);\n            *(result++) = static_cast<uint8_t>((cp & 0x3f)          | 0x80);\n        }\n        else if (cp < 0x10000) {              // three octets\n            *(result++) = static_cast<uint8_t>((cp >> 12)           | 0xe0);\n            *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f)   | 0x80);\n            *(result++) = static_cast<uint8_t>((cp & 0x3f)          | 0x80);\n        }\n        else {                                // four octets\n            *(result++) = static_cast<uint8_t>((cp >> 18)           | 0xf0);\n            *(result++) = static_cast<uint8_t>(((cp >> 12) & 0x3f)  | 0x80);\n            *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f)   | 0x80);\n            *(result++) = static_cast<uint8_t>((cp & 0x3f)          | 0x80);\n        }\n        return result;\n    }\n\n    template <typename octet_iterator, typename output_iterator>\n    output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement)\n    {\n        while (start != end) {\n            octet_iterator sequence_start = start;\n            internal::utf_error err_code = utf8::internal::validate_next(start, end);\n            switch (err_code) {\n                case internal::UTF8_OK :\n                    for (octet_iterator it = sequence_start; it != start; ++it)\n                        *out++ = *it;\n                    break;\n                case internal::NOT_ENOUGH_ROOM:\n                    throw not_enough_room();\n                case internal::INVALID_LEAD:\n                    out = utf8::append (replacement, out);\n                    ++start;\n                    break;\n                case internal::INCOMPLETE_SEQUENCE:\n                case internal::OVERLONG_SEQUENCE:\n                case internal::INVALID_CODE_POINT:\n                    out = utf8::append (replacement, out);\n                    ++start;\n                    // just one replacement mark for the sequence\n                    while (start != end && utf8::internal::is_trail(*start))\n                        ++start;\n                    break;\n            }\n        }\n        return out;\n    }\n\n    template <typename octet_iterator, typename output_iterator>\n    inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out)\n    {\n        static const uint32_t replacement_marker = utf8::internal::mask16(0xfffd);\n        return utf8::replace_invalid(start, end, out, replacement_marker);\n    }\n\n    template <typename octet_iterator>\n    uint32_t next(octet_iterator& it, octet_iterator end)\n    {\n        uint32_t cp = 0;\n        internal::utf_error err_code = utf8::internal::validate_next(it, end, cp);\n        switch (err_code) {\n            case internal::UTF8_OK :\n                break;\n            case internal::NOT_ENOUGH_ROOM :\n                throw not_enough_room();\n            case internal::INVALID_LEAD :\n            case internal::INCOMPLETE_SEQUENCE :\n            case internal::OVERLONG_SEQUENCE :\n                throw invalid_utf8(*it);\n            case internal::INVALID_CODE_POINT :\n                throw invalid_code_point(cp);\n        }\n        return cp;\n    }\n\n    template <typename octet_iterator>\n    uint32_t peek_next(octet_iterator it, octet_iterator end)\n    {\n        return utf8::next(it, end);\n    }\n\n    template <typename octet_iterator>\n    uint32_t prior(octet_iterator& it, octet_iterator start)\n    {\n        // can't do much if it == start\n        if (it == start)\n            throw not_enough_room();\n\n        octet_iterator end = it;\n        // Go back until we hit either a lead octet or start\n        while (utf8::internal::is_trail(*(--it)))\n            if (it == start)\n                throw invalid_utf8(*it); // error - no lead byte in the sequence\n        return utf8::peek_next(it, end);\n    }\n\n    /// Deprecated in versions that include \"prior\"\n    template <typename octet_iterator>\n    uint32_t previous(octet_iterator& it, octet_iterator pass_start)\n    {\n        octet_iterator end = it;\n        while (utf8::internal::is_trail(*(--it)))\n            if (it == pass_start)\n                throw invalid_utf8(*it); // error - no lead byte in the sequence\n        octet_iterator temp = it;\n        return utf8::next(temp, end);\n    }\n\n    template <typename octet_iterator, typename distance_type>\n    void advance (octet_iterator& it, distance_type n, octet_iterator end)\n    {\n        for (distance_type i = 0; i < n; ++i)\n            utf8::next(it, end);\n    }\n\n    template <typename octet_iterator>\n    typename std::iterator_traits<octet_iterator>::difference_type\n    distance (octet_iterator first, octet_iterator last)\n    {\n        typename std::iterator_traits<octet_iterator>::difference_type dist;\n        for (dist = 0; first < last; ++dist)\n            utf8::next(first, last);\n        return dist;\n    }\n\n    template <typename u16bit_iterator, typename octet_iterator>\n    octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result)\n    {\n        while (start != end) {\n            uint32_t cp = utf8::internal::mask16(*start++);\n            // Take care of surrogate pairs first\n            if (utf8::internal::is_lead_surrogate(cp)) {\n                if (start != end) {\n                    uint32_t trail_surrogate = utf8::internal::mask16(*start++);\n                    if (utf8::internal::is_trail_surrogate(trail_surrogate))\n                        cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET;\n                    else\n                        throw invalid_utf16(static_cast<uint16_t>(trail_surrogate));\n                }\n                else\n                    throw invalid_utf16(static_cast<uint16_t>(cp));\n\n            }\n            // Lone trail surrogate\n            else if (utf8::internal::is_trail_surrogate(cp))\n                throw invalid_utf16(static_cast<uint16_t>(cp));\n\n            result = utf8::append(cp, result);\n        }\n        return result;\n    }\n\n    template <typename u16bit_iterator, typename octet_iterator>\n    u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result)\n    {\n        while (start != end) {\n            uint32_t cp = utf8::next(start, end);\n            if (cp > 0xffff) { //make a surrogate pair\n                *result++ = static_cast<uint16_t>((cp >> 10)   + internal::LEAD_OFFSET);\n                *result++ = static_cast<uint16_t>((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN);\n            }\n            else\n                *result++ = static_cast<uint16_t>(cp);\n        }\n        return result;\n    }\n\n    template <typename octet_iterator, typename u32bit_iterator>\n    octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result)\n    {\n        while (start != end)\n            result = utf8::append(*(start++), result);\n\n        return result;\n    }\n\n    template <typename octet_iterator, typename u32bit_iterator>\n    u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result)\n    {\n        while (start != end)\n            (*result++) = utf8::next(start, end);\n\n        return result;\n    }\n\n    // The iterator class\n    template <typename octet_iterator>\n    class iterator : public std::iterator <std::bidirectional_iterator_tag, uint32_t> {\n      octet_iterator it;\n      octet_iterator range_start;\n      octet_iterator range_end;\n      public:\n      iterator () {}\n      explicit iterator (const octet_iterator& octet_it,\n                         const octet_iterator& range_start,\n                         const octet_iterator& range_end) :\n               it(octet_it), range_start(range_start), range_end(range_end)\n      {\n          if (it < range_start || it > range_end)\n              throw std::out_of_range(\"Invalid utf-8 iterator position\");\n      }\n      // the default \"big three\" are OK\n      octet_iterator base () const { return it; }\n      uint32_t operator * () const\n      {\n          octet_iterator temp = it;\n          return utf8::next(temp, range_end);\n      }\n      bool operator == (const iterator& rhs) const\n      {\n          if (range_start != rhs.range_start || range_end != rhs.range_end)\n              throw std::logic_error(\"Comparing utf-8 iterators defined with different ranges\");\n          return (it == rhs.it);\n      }\n      bool operator != (const iterator& rhs) const\n      {\n          return !(operator == (rhs));\n      }\n      iterator& operator ++ ()\n      {\n          utf8::next(it, range_end);\n          return *this;\n      }\n      iterator operator ++ (int)\n      {\n          iterator temp = *this;\n          utf8::next(it, range_end);\n          return temp;\n      }\n      iterator& operator -- ()\n      {\n          utf8::prior(it, range_start);\n          return *this;\n      }\n      iterator operator -- (int)\n      {\n          iterator temp = *this;\n          utf8::prior(it, range_start);\n          return temp;\n      }\n    }; // class iterator\n\n} // namespace utf8\n\n#endif //header guard\n\n\n"
  },
  {
    "path": "app/utf8/utf8/core.h",
    "content": "// Copyright 2006 Nemanja Trifunovic\n\n/*\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n\n#ifndef UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731\n#define UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731\n\n#include <iterator>\n\nnamespace utf8\n{\n    // The typedefs for 8-bit, 16-bit and 32-bit unsigned integers\n    // You may need to change them to match your system.\n    // These typedefs have the same names as ones from cstdint, or boost/cstdint\n    typedef unsigned char   uint8_t;\n    typedef unsigned short  uint16_t;\n    typedef unsigned int    uint32_t;\n\n// Helper code - not intended to be directly called by the library users. May be changed at any time\nnamespace internal\n{\n    // Unicode constants\n    // Leading (high) surrogates: 0xd800 - 0xdbff\n    // Trailing (low) surrogates: 0xdc00 - 0xdfff\n    const uint16_t LEAD_SURROGATE_MIN  = 0xd800u;\n    const uint16_t LEAD_SURROGATE_MAX  = 0xdbffu;\n    const uint16_t TRAIL_SURROGATE_MIN = 0xdc00u;\n    const uint16_t TRAIL_SURROGATE_MAX = 0xdfffu;\n    const uint16_t LEAD_OFFSET         = LEAD_SURROGATE_MIN - (0x10000 >> 10);\n    const uint32_t SURROGATE_OFFSET    = 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN;\n\n    // Maximum valid value for a Unicode code point\n    const uint32_t CODE_POINT_MAX      = 0x0010ffffu;\n\n    template<typename octet_type>\n    inline uint8_t mask8(octet_type oc)\n    {\n        return static_cast<uint8_t>(0xff & oc);\n    }\n    template<typename u16_type>\n    inline uint16_t mask16(u16_type oc)\n    {\n        return static_cast<uint16_t>(0xffff & oc);\n    }\n    template<typename octet_type>\n    inline bool is_trail(octet_type oc)\n    {\n        return ((utf8::internal::mask8(oc) >> 6) == 0x2);\n    }\n\n    template <typename u16>\n    inline bool is_lead_surrogate(u16 cp)\n    {\n        return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX);\n    }\n\n    template <typename u16>\n    inline bool is_trail_surrogate(u16 cp)\n    {\n        return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX);\n    }\n\n    template <typename u16>\n    inline bool is_surrogate(u16 cp)\n    {\n        return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX);\n    }\n\n    template <typename u32>\n    inline bool is_code_point_valid(u32 cp)\n    {\n        return (cp <= CODE_POINT_MAX && !utf8::internal::is_surrogate(cp));\n    }\n\n    template <typename octet_iterator>\n    inline typename std::iterator_traits<octet_iterator>::difference_type\n    sequence_length(octet_iterator lead_it)\n    {\n        uint8_t lead = utf8::internal::mask8(*lead_it);\n        if (lead < 0x80)\n            return 1;\n        else if ((lead >> 5) == 0x6)\n            return 2;\n        else if ((lead >> 4) == 0xe)\n            return 3;\n        else if ((lead >> 3) == 0x1e)\n            return 4;\n        else\n            return 0;\n    }\n\n    template <typename octet_difference_type>\n    inline bool is_overlong_sequence(uint32_t cp, octet_difference_type length)\n    {\n        if (cp < 0x80) {\n            if (length != 1) \n                return true;\n        }\n        else if (cp < 0x800) {\n            if (length != 2) \n                return true;\n        }\n        else if (cp < 0x10000) {\n            if (length != 3) \n                return true;\n        }\n\n        return false;\n    }\n\n    enum utf_error {UTF8_OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT};\n\n    /// Helper for get_sequence_x\n    template <typename octet_iterator>\n    utf_error increase_safely(octet_iterator& it, octet_iterator end)\n    {\n        if (++it == end)\n            return NOT_ENOUGH_ROOM;\n\n        if (!utf8::internal::is_trail(*it))\n            return INCOMPLETE_SEQUENCE;\n        \n        return UTF8_OK;\n    }\n\n    #define UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(IT, END) {utf_error ret = increase_safely(IT, END); if (ret != UTF8_OK) return ret;}    \n\n    /// get_sequence_x functions decode utf-8 sequences of the length x\n    template <typename octet_iterator>\n    utf_error get_sequence_1(octet_iterator& it, octet_iterator end, uint32_t& code_point)\n    {\n        if (it == end)\n            return NOT_ENOUGH_ROOM;\n\n        code_point = utf8::internal::mask8(*it);\n\n        return UTF8_OK;\n    }\n\n    template <typename octet_iterator>\n    utf_error get_sequence_2(octet_iterator& it, octet_iterator end, uint32_t& code_point)\n    {\n        if (it == end) \n            return NOT_ENOUGH_ROOM;\n        \n        code_point = utf8::internal::mask8(*it);\n\n        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)\n\n        code_point = ((code_point << 6) & 0x7ff) + ((*it) & 0x3f);\n\n        return UTF8_OK;\n    }\n\n    template <typename octet_iterator>\n    utf_error get_sequence_3(octet_iterator& it, octet_iterator end, uint32_t& code_point)\n    {\n        if (it == end)\n            return NOT_ENOUGH_ROOM;\n            \n        code_point = utf8::internal::mask8(*it);\n\n        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)\n\n        code_point = ((code_point << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff);\n\n        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)\n\n        code_point += (*it) & 0x3f;\n\n        return UTF8_OK;\n    }\n\n    template <typename octet_iterator>\n    utf_error get_sequence_4(octet_iterator& it, octet_iterator end, uint32_t& code_point)\n    {\n        if (it == end)\n           return NOT_ENOUGH_ROOM;\n\n        code_point = utf8::internal::mask8(*it);\n\n        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)\n\n        code_point = ((code_point << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff);\n\n        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)\n\n        code_point += (utf8::internal::mask8(*it) << 6) & 0xfff;\n\n        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)\n\n        code_point += (*it) & 0x3f;\n\n        return UTF8_OK;\n    }\n\n    #undef UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR\n\n    template <typename octet_iterator>\n    utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t& code_point)\n    {\n        // Save the original value of it so we can go back in case of failure\n        // Of course, it does not make much sense with i.e. stream iterators\n        octet_iterator original_it = it;\n\n        uint32_t cp = 0;\n        // Determine the sequence length based on the lead octet\n        typedef typename std::iterator_traits<octet_iterator>::difference_type octet_difference_type;\n        const octet_difference_type length = utf8::internal::sequence_length(it);\n\n        // Get trail octets and calculate the code point\n        utf_error err = UTF8_OK;\n        switch (length) {\n            case 0: \n                return INVALID_LEAD;\n            case 1:\n                err = utf8::internal::get_sequence_1(it, end, cp);\n                break;\n            case 2:\n                err = utf8::internal::get_sequence_2(it, end, cp);\n            break;\n            case 3:\n                err = utf8::internal::get_sequence_3(it, end, cp);\n            break;\n            case 4:\n                err = utf8::internal::get_sequence_4(it, end, cp);\n            break;\n        }\n\n        if (err == UTF8_OK) {\n            // Decoding succeeded. Now, security checks...\n            if (utf8::internal::is_code_point_valid(cp)) {\n                if (!utf8::internal::is_overlong_sequence(cp, length)){\n                    // Passed! Return here.\n                    code_point = cp;\n                    ++it;\n                    return UTF8_OK;\n                }\n                else\n                    err = OVERLONG_SEQUENCE;\n            }\n            else \n                err = INVALID_CODE_POINT;\n        }\n\n        // Failure branch - restore the original value of the iterator\n        it = original_it;\n        return err;\n    }\n\n    template <typename octet_iterator>\n    inline utf_error validate_next(octet_iterator& it, octet_iterator end) {\n        uint32_t ignored;\n        return utf8::internal::validate_next(it, end, ignored);\n    }\n\n} // namespace internal\n\n    /// The library API - functions intended to be called by the users\n\n    // Byte order mark\n    const uint8_t bom[] = {0xef, 0xbb, 0xbf};\n\n    template <typename octet_iterator>\n    octet_iterator find_invalid(octet_iterator start, octet_iterator end)\n    {\n        octet_iterator result = start;\n        while (result != end) {\n            utf8::internal::utf_error err_code = utf8::internal::validate_next(result, end);\n            if (err_code != internal::UTF8_OK)\n                return result;\n        }\n        return result;\n    }\n\n    template <typename octet_iterator>\n    inline bool is_valid(octet_iterator start, octet_iterator end)\n    {\n        return (utf8::find_invalid(start, end) == end);\n    }\n\n    template <typename octet_iterator>\n    inline bool starts_with_bom (octet_iterator it, octet_iterator end)\n    {\n        return (\n            ((it != end) && (utf8::internal::mask8(*it++)) == bom[0]) &&\n            ((it != end) && (utf8::internal::mask8(*it++)) == bom[1]) &&\n            ((it != end) && (utf8::internal::mask8(*it))   == bom[2])\n           );\n    }\n\t\n    //Deprecated in release 2.3 \n    template <typename octet_iterator>\n    inline bool is_bom (octet_iterator it)\n    {\n        return (\n            (utf8::internal::mask8(*it++)) == bom[0] &&\n            (utf8::internal::mask8(*it++)) == bom[1] &&\n            (utf8::internal::mask8(*it))   == bom[2]\n           );\n    }\n} // namespace utf8\n\n#endif // header guard\n\n\n"
  },
  {
    "path": "app/utf8/utf8/unchecked.h",
    "content": "// Copyright 2006 Nemanja Trifunovic\n\n/*\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n\n#ifndef UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731\n#define UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731\n\n#include \"core.h\"\n\nnamespace utf8\n{\n    namespace unchecked \n    {\n        template <typename octet_iterator>\n        octet_iterator append(uint32_t cp, octet_iterator result)\n        {\n            if (cp < 0x80)                        // one octet\n                *(result++) = static_cast<uint8_t>(cp);  \n            else if (cp < 0x800) {                // two octets\n                *(result++) = static_cast<uint8_t>((cp >> 6)          | 0xc0);\n                *(result++) = static_cast<uint8_t>((cp & 0x3f)        | 0x80);\n            }\n            else if (cp < 0x10000) {              // three octets\n                *(result++) = static_cast<uint8_t>((cp >> 12)         | 0xe0);\n                *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f) | 0x80);\n                *(result++) = static_cast<uint8_t>((cp & 0x3f)        | 0x80);\n            }\n            else {                                // four octets\n                *(result++) = static_cast<uint8_t>((cp >> 18)         | 0xf0);\n                *(result++) = static_cast<uint8_t>(((cp >> 12) & 0x3f)| 0x80);\n                *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f) | 0x80);\n                *(result++) = static_cast<uint8_t>((cp & 0x3f)        | 0x80);\n            }\n            return result;\n        }\n\n        template <typename octet_iterator>\n        uint32_t next(octet_iterator& it)\n        {\n            uint32_t cp = utf8::internal::mask8(*it);\n            typename std::iterator_traits<octet_iterator>::difference_type length = utf8::internal::sequence_length(it);\n            switch (length) {\n                case 1:\n                    break;\n                case 2:\n                    it++;\n                    cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f);\n                    break;\n                case 3:\n                    ++it; \n                    cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff);\n                    ++it;\n                    cp += (*it) & 0x3f;\n                    break;\n                case 4:\n                    ++it;\n                    cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff);                \n                    ++it;\n                    cp += (utf8::internal::mask8(*it) << 6) & 0xfff;\n                    ++it;\n                    cp += (*it) & 0x3f; \n                    break;\n            }\n            ++it;\n            return cp;        \n        }\n\n        template <typename octet_iterator>\n        uint32_t peek_next(octet_iterator it)\n        {\n            return utf8::unchecked::next(it);    \n        }\n\n        template <typename octet_iterator>\n        uint32_t prior(octet_iterator& it)\n        {\n            while (utf8::internal::is_trail(*(--it))) ;\n            octet_iterator temp = it;\n            return utf8::unchecked::next(temp);\n        }\n\n        // Deprecated in versions that include prior, but only for the sake of consistency (see utf8::previous)\n        template <typename octet_iterator>\n        inline uint32_t previous(octet_iterator& it)\n        {\n            return utf8::unchecked::prior(it);\n        }\n\n        template <typename octet_iterator, typename distance_type>\n        void advance (octet_iterator& it, distance_type n)\n        {\n            for (distance_type i = 0; i < n; ++i)\n                utf8::unchecked::next(it);\n        }\n\n        template <typename octet_iterator>\n        typename std::iterator_traits<octet_iterator>::difference_type\n        distance (octet_iterator first, octet_iterator last)\n        {\n            typename std::iterator_traits<octet_iterator>::difference_type dist;\n            for (dist = 0; first < last; ++dist) \n                utf8::unchecked::next(first);\n            return dist;\n        }\n\n        template <typename u16bit_iterator, typename octet_iterator>\n        octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result)\n        {       \n            while (start != end) {\n                uint32_t cp = utf8::internal::mask16(*start++);\n            // Take care of surrogate pairs first\n                if (utf8::internal::is_lead_surrogate(cp)) {\n                    uint32_t trail_surrogate = utf8::internal::mask16(*start++);\n                    cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET;\n                }\n                result = utf8::unchecked::append(cp, result);\n            }\n            return result;         \n        }\n\n        template <typename u16bit_iterator, typename octet_iterator>\n        u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result)\n        {\n            while (start < end) {\n                uint32_t cp = utf8::unchecked::next(start);\n                if (cp > 0xffff) { //make a surrogate pair\n                    *result++ = static_cast<uint16_t>((cp >> 10)   + internal::LEAD_OFFSET);\n                    *result++ = static_cast<uint16_t>((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN);\n                }\n                else\n                    *result++ = static_cast<uint16_t>(cp);\n            }\n            return result;\n        }\n\n        template <typename octet_iterator, typename u32bit_iterator>\n        octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result)\n        {\n            while (start != end)\n                result = utf8::unchecked::append(*(start++), result);\n\n            return result;\n        }\n\n        template <typename octet_iterator, typename u32bit_iterator>\n        u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result)\n        {\n            while (start < end)\n                (*result++) = utf8::unchecked::next(start);\n\n            return result;\n        }\n\n        // The iterator class\n        template <typename octet_iterator>\n          class iterator : public std::iterator <std::bidirectional_iterator_tag, uint32_t> { \n            octet_iterator it;\n            public:\n            iterator () {}\n            explicit iterator (const octet_iterator& octet_it): it(octet_it) {}\n            // the default \"big three\" are OK\n            octet_iterator base () const { return it; }\n            uint32_t operator * () const\n            {\n                octet_iterator temp = it;\n                return utf8::unchecked::next(temp);\n            }\n            bool operator == (const iterator& rhs) const \n            { \n                return (it == rhs.it);\n            }\n            bool operator != (const iterator& rhs) const\n            {\n                return !(operator == (rhs));\n            }\n            iterator& operator ++ () \n            {\n                ::std::advance(it, utf8::internal::sequence_length(it));\n                return *this;\n            }\n            iterator operator ++ (int)\n            {\n                iterator temp = *this;\n                ::std::advance(it, utf8::internal::sequence_length(it));\n                return temp;\n            }  \n            iterator& operator -- ()\n            {\n                utf8::unchecked::prior(it);\n                return *this;\n            }\n            iterator operator -- (int)\n            {\n                iterator temp = *this;\n                utf8::unchecked::prior(it);\n                return temp;\n            }\n          }; // class iterator\n\n    } // namespace utf8::unchecked\n} // namespace utf8 \n\n\n#endif // header guard\n\n"
  },
  {
    "path": "app/utf8/utf8.h",
    "content": "// Copyright 2006 Nemanja Trifunovic\r\n\r\n/*\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n\r\n#ifndef UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731\r\n#define UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731\r\n\r\n#include \"utf8/checked.h\"\r\n#include \"utf8/unchecked.h\"\r\n\r\n#endif // header guard\r\n"
  },
  {
    "path": "app/win/fontedit.rc",
    "content": "IDI_ICON1               ICON    \"fontedit.ico\"\n"
  },
  {
    "path": "app/x11/fontedit.desktop",
    "content": "[Desktop Entry]\nExec=fontedit\nName=FontEdit\nIcon=fontedit\nType=Application\nCategories=Qt;Development;\nComment=Font image to bytes converter\nGenericName=Font to Source Code Converter\nTerminal=false\n"
  },
  {
    "path": "fontedit.iss",
    "content": "; Script generated by the Inno Setup Script Wizard.\n; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!\n\n#define MyAppName \"FontEdit\"\n#define MyAppVersion \"1.0.0\"\n#define MyAppPublisher \"Dominik Kapusta\"\n#define MyAppURL \"https://www.kapusta.cc\"\n#define MyAppExeName \"FontEdit.exe\"\n\n[Setup]\n; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.\n; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)\nAppId={{D7FE7A75-E913-4479-8798-E5626C192A37}\nAppName={#MyAppName}\nAppVersion={#MyAppVersion}\n;AppVerName={#MyAppName} {#MyAppVersion}\nAppPublisher={#MyAppPublisher}\nAppPublisherURL={#MyAppURL}\nAppSupportURL={#MyAppURL}\nAppUpdatesURL={#MyAppURL}\nDefaultDirName={autopf}\\{#MyAppName}\nDisableProgramGroupPage=yes\nLicenseFile=C:\\Users\\ayoy\\fontedit\\LICENSE\n; Uncomment the following line to run in non administrative install mode (install for current user only.)\n;PrivilegesRequired=lowest\nOutputBaseFilename=FontEdit\nCompression=lzma\nSolidCompression=yes\nWizardStyle=modern\n\n[Languages]\nName: \"english\"; MessagesFile: \"compiler:Default.isl\"\n\n[Tasks]\nName: \"desktopicon\"; Description: \"{cm:CreateDesktopIcon}\"; GroupDescription: \"{cm:AdditionalIcons}\"; Flags: unchecked\n\n[Files]\nSource: \"C:\\Users\\ayoy\\build-fontedit-Desktop_Qt_5_14_1_MinGW_64_bit-Release\\FontEdit.exe\"; DestDir: \"{app}\"; Flags: ignoreversion\nSource: \"C:\\Users\\ayoy\\build-fontedit-Desktop_Qt_5_14_1_MinGW_64_bit-Release\\win\\*\"; DestDir: \"{app}\"; Flags: ignoreversion recursesubdirs createallsubdirs\nSource: \"C:\\Qt\\Tools\\OpenSSL\\Win_x64\\bin\\lib*.dll\"; DestDir: \"{app}\"; Flags: ignoreversion recursesubdirs createallsubdirs\n; NOTE: Don't use \"Flags: ignoreversion\" on any shared system files\n\n[Icons]\nName: \"{autoprograms}\\{#MyAppName}\"; Filename: \"{app}\\{#MyAppExeName}\"\nName: \"{autodesktop}\\{#MyAppName}\"; Filename: \"{app}\\{#MyAppExeName}\"; Tasks: desktopicon\n\n[Run]\nFilename: \"{app}\\{#MyAppExeName}\"; Description: \"{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}\"; Flags: nowait postinstall skipifsilent\n\n"
  },
  {
    "path": "lib/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.9)\nproject(libFontEdit)\n\nset(LIBFONTEDIT_STANDALONE_PROJECT OFF)\nif (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)\n    set(LIBFONTEDIT_STANDALONE_PROJECT ON)\nendif ()\n\nadd_subdirectory(src)\n\nenable_testing()\nif(${BUILD_TESTS})\n    add_subdirectory(test)\nendif()\n"
  },
  {
    "path": "lib/src/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.9)\n\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\nproject(font2bytes)\n\nset(HEADERS\n    f2b.h\n    fontdata.h\n    fontsourcecodegenerator.h\n    format.h\n    sourcecode.h\n    )\n\nset(SOURCES\n    fontdata.cpp\n    fontsourcecodegenerator.cpp\n    )\n\nif (LIBFONTEDIT_STANDALONE_PROJECT)\n    add_library(${PROJECT_NAME}\n        SHARED\n        ${HEADERS}\n        ${SOURCES}\n        )\n    \n    set_target_properties(${PROJECT_NAME} PROPERTIES\n        SOVERSION 1.1.0\n        LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)\n\nelse ()\n    add_library(${PROJECT_NAME} ${HEADERS} ${SOURCES})\nendif ()\n\ntarget_link_libraries(${PROJECT_NAME} GSL)\ntarget_include_directories(${PROJECT_NAME} PUBLIC\n\t${CMAKE_CURRENT_SOURCE_DIR}\n    )\n\nif (UNIX AND NOT APPLE)\n    if (LIBFONTEDIT_STANDALONE_PROJECT)\n        INSTALL(FILES ${HEADERS} DESTINATION include/f2b)\n        INSTALL(FILES include/f2b.h DESTINATION include)\n\n        INSTALL(TARGETS ${PROJECT_NAME} \n            LIBRARY DESTINATION lib\n            ARCHIVE DESTINATION lib)\n    endif ()\nendif ()\n\n"
  },
  {
    "path": "lib/src/f2b.h",
    "content": "#ifndef F2B_PRIVATE_H\n#define F2B_PRIVATE_H\n\n#include \"fontdata.h\"\n#include \"fontsourcecodegenerator.h\"\n#include \"format.h\"\n#include \"sourcecode.h\"\n\n#endif // F2B_PRIVATE_H\n"
  },
  {
    "path": "lib/src/fontdata.cpp",
    "content": "#include \"fontdata.h\"\n#include <algorithm>\n\nnamespace f2b {\n\nnamespace font {\n\nglyph::glyph(font::glyph_size sz) :\n    size_ { sz },\n    pixels_ { std::vector<bool>(sz.width * sz.height, false) }\n{}\n\nglyph::glyph(font::glyph_size sz, std::vector<bool> pixels) :\n    size_ { sz },\n    pixels_ { std::move(pixels) }\n{\n    if (pixels_.size() != sz.width * sz.height) {\n        throw std::logic_error { \"pixels size must equal glyph size (width * height)\" };\n    }\n}\n\nvoid glyph::clear()\n{\n    pixels_ = std::vector<bool>(size_.width * size_.height, false);\n}\n\nstd::size_t glyph::top_margin() const\n{\n    auto first_set_pixel = std::find(pixels_.begin(), pixels_.end(), true);\n    return static_cast<std::size_t>(std::distance(pixels_.begin(), first_set_pixel)) / size_.width;\n}\n\nstd::size_t glyph::bottom_margin() const\n{\n    auto last_set_pixel = std::find(pixels_.rbegin(), pixels_.rend(), true);\n    return static_cast<std::size_t>(std::distance(pixels_.rbegin(), last_set_pixel)) / size_.width;\n}\n\n\nface::face(const face_reader &data) :\n    face(data.font_size(), read_glyphs(data))\n{\n    for (std::size_t i = 0; i < glyphs_.size(); i++) {\n        exported_glyph_ids_.insert(i);\n    }\n}\n\nface::face(font::glyph_size size, std::vector<glyph> glyphs, std::set<std::size_t> exported_glyph_ids) :\n    sz_ { size },\n    glyphs_ { std::move(glyphs) },\n    exported_glyph_ids_ { std::move(exported_glyph_ids) }\n{}\n\nstd::vector<glyph> face::read_glyphs(const face_reader &data)\n{\n    std::vector<glyph> glyphs;\n    glyphs.reserve(data.num_glyphs());\n\n    const std::vector<bool>::size_type pixels_per_glyph { data.font_size().width * data.font_size().height };\n\n    for (std::size_t i = 0; i < data.num_glyphs(); i++) {\n\n        std::vector<bool> pixels;\n        pixels.reserve(pixels_per_glyph);\n\n        for (std::size_t y = 0; y < data.font_size().height; y++) {\n            for (std::size_t x = 0; x < data.font_size().width; x++) {\n                point p { x, y };\n                pixels.push_back(data.is_pixel_set(i, p));\n            }\n        }\n\n        glyph g { data.font_size(), pixels };\n        glyphs.push_back(g);\n    }\n\n    return glyphs;\n}\n\nmargins face::calculate_margins() const noexcept\n{\n    margins m {sz_.height, sz_.height};\n\n    std::for_each(glyphs_.begin(), glyphs_.end(), [&](const font::glyph& g) {\n        m.top = std::min(m.top, g.top_margin());\n        m.bottom = std::min(m.bottom, g.bottom_margin());\n    });\n\n    return m;\n}\n\n} // namespace Font\n} // namespace f2b\n"
  },
  {
    "path": "lib/src/fontdata.h",
    "content": "#ifndef FONTDATA_H\n#define FONTDATA_H\n\n#include <vector>\n#include <iostream>\n#include <set>\n\nnamespace f2b {\n\nnamespace font {\n\n/**\n * @brief A struct that describes font top and bottom margins\n *        (lines where no glyphs are drawn).\n */\nstruct margins {\n    std::size_t top;\n    std::size_t bottom;\n};\n\ninline bool operator==(const margins& lhs, const margins& rhs) noexcept {\n    return lhs.top == rhs.top && lhs.bottom == rhs.bottom;\n}\n\ninline bool operator!=(const margins& lhs, const margins& rhs) noexcept {\n    return !(lhs == rhs);\n}\n\n\n/**\n * @brief A struct that describes font size (glyph size) in pixels.\n */\nstruct glyph_size\n{\n    std::size_t width;\n    std::size_t height;\n\n    glyph_size with_margins(const margins& m) {\n        glyph_size size { width, height };\n        auto m_height = m.top + m.bottom;\n        if (m_height > height) {\n            size.height = 0;\n        } else {\n            size.height -= m_height;\n        }\n        return size;\n    }\n};\n\ninline bool operator==(const glyph_size& lhs, const glyph_size& rhs) noexcept {\n    return lhs.width == rhs.width && lhs.height == rhs.height;\n}\ninline bool operator!=(const glyph_size& lhs, const glyph_size& rhs) noexcept {\n    return !(lhs == rhs);\n}\n\n\n/**\n * @brief A struct that describes a point in font glyph coordinates.\n */\nstruct point\n{\n    std::size_t x;\n    std::size_t y;\n\n    std::size_t offset(glyph_size sz) { return y * sz.width + x; }\n};\n\ninline bool operator==(const point& lhs, const point& rhs) noexcept {\n    return lhs.x == rhs.x && lhs.y == rhs.y;\n}\n\ninline bool operator!=(const point& lhs, const point& rhs) noexcept {\n    return !(lhs == rhs);\n}\n\n/**\n * @brief A class that describes a single Font Glyph.\n *\n * A Glyph is represented by a std::vector<bool> of a fixed size.\n */\nclass glyph\n{\npublic:\n    explicit glyph(glyph_size sz = {});\n    explicit glyph(glyph_size sz, std::vector<bool> pixels);\n\n    f2b::font::glyph_size size() const noexcept { return size_; }\n    bool is_pixel_set(point p) const { return pixels_[p.offset(size_)]; }\n    void set_pixel_set(point p, bool is_set) { pixels_[p.offset(size_)] = is_set; }\n    void clear();\n\n    const std::vector<bool>& pixels() const noexcept { return pixels_; }\n\n    std::size_t top_margin() const;\n    std::size_t bottom_margin() const;\n\nprivate:\n    font::glyph_size size_;\n    std::vector<bool> pixels_;\n};\n\ninline bool operator==(const glyph& lhs, const glyph& rhs) noexcept {\n    return lhs.size() == rhs.size() && lhs.pixels() == rhs.pixels();\n}\n\ninline bool operator!=(const glyph& lhs, const glyph& rhs) noexcept {\n    return !(lhs == rhs);\n}\n\n\n/**\n * @brief An abstract class defining an interface for a font face reader.\n *\n * FaceReader instance can be passed as a \"data source\" for the Face constructor.\n */\nclass face_reader\n{\npublic:\n    virtual glyph_size font_size() const = 0;\n    virtual std::size_t num_glyphs() const = 0;\n    virtual bool is_pixel_set(std::size_t glyph_id, point p) const = 0;\n\n    virtual ~face_reader() = default;\n};\n\n\n/**\n * @brief A class describing a font face (a set of glyphs for a specific\n *        combination of font family, pixel size and weight).\n */\nclass face\n{\npublic:\n\n    /// The default constructor intializing an empty face\n    explicit face() = default;\n\n    /// The constructor reading a face using face reader. By default all glyphs are exported.\n    explicit face(const face_reader &data);\n\n    /// The constructor initializing a face with a given size, vector of glyphs and exported glyph IDs.\n    explicit face(glyph_size glyphs_size, std::vector<glyph> glyphs, std::set<std::size_t> exported_glyph_ids = {});\n\n    f2b::font::glyph_size glyphs_size() const noexcept { return sz_; }\n    std::size_t num_glyphs() const noexcept { return glyphs_.size(); }\n\n    glyph& glyph_at(std::size_t index) { return glyphs_.at(index); }\n    const glyph& glyph_at(std::size_t index) const { return glyphs_.at(index); }\n\n    std::set<std::size_t>& exported_glyph_ids() { return exported_glyph_ids_; }\n    const std::set<std::size_t>& exported_glyph_ids() const { return exported_glyph_ids_; }\n\n    const std::vector<glyph>& glyphs() const { return glyphs_; }\n    void set_glyph(glyph g, std::size_t index) { glyphs_[index] = g; }\n    void append_glyph(glyph g) { glyphs_.push_back(std::move(g)); }\n    void delete_last_glyph() { if (glyphs_.size() > 0) glyphs_.pop_back(); }\n    void clear_glyph(std::size_t index) {\n        if (index >= glyphs_.size()) {\n            throw std::out_of_range { \"Glyph index out of range\" };\n        }\n        glyphs_[index].clear();\n    }\n\n    glyph& operator[](char ascii) {\n        if (ascii < ' ') {\n            throw std::out_of_range { \"Glyphs for 0-31 ASCII range are not supported\" };\n        }\n        return glyphs_[ascii-32];\n    }\n\n    const glyph& operator[](char ascii) const {\n        if (ascii < ' ') {\n            throw std::out_of_range { \"Glyphs for 0-31 ASCII range are not supported\" };\n        }\n        return glyphs_[ascii-32];\n    }\n\n    /**\n     * Calculcates margins of a face.\n     *\n     * This method analyzes all glyphs and takes the maximum common empty areas\n     * on the top and bottom.\n     */\n    margins calculate_margins() const noexcept;\n\nprivate:\n    static std::vector<glyph> read_glyphs(const face_reader &data);\n\n    font::glyph_size sz_;\n    std::vector<glyph> glyphs_;\n    std::set<std::size_t> exported_glyph_ids_;\n};\n\ninline bool operator==(const face& lhs, const face& rhs) noexcept {\n    return lhs.glyphs_size() == rhs.glyphs_size() && lhs.glyphs() == rhs.glyphs();\n}\n\ninline bool operator!=(const face& lhs, const face& rhs) noexcept {\n    return !(lhs == rhs);\n}\n\n} // namespace font\n\n} // namespace f2b\n\ninline std::ostream& operator<<(std::ostream& os, const f2b::font::glyph& g) {\n\n    std::size_t col = 0;\n    for (auto p : g.pixels()) {\n        os << p;\n        if (++col >= g.size().width) {\n            col = 0;\n            os << std::endl;\n        }\n    }\n    os << std::flush;\n\n    return os;\n}\n\n\n#endif // FONTDATA_H\n"
  },
  {
    "path": "lib/src/fontsourcecodegenerator.cpp",
    "content": "#include \"fontsourcecodegenerator.h\"\n#include <iomanip>\n#include <string>\n\nnamespace f2b {\n\nfont::margins pixel_margins(font::margins line_margins, font::glyph_size glyph_size)\n{\n    return { line_margins.top * glyph_size.width, line_margins.bottom * glyph_size.width };\n}\n\nstd::string font_source_code_generator::current_timestamp()\n{\n    auto t = std::time(nullptr);\n    auto tm = *std::localtime(&t);\n    std::ostringstream s;\n    s << std::put_time(&tm, \"%d-%m-%Y %H:%M:%S\");\n    return s.str();\n}\n\nstd::string font_source_code_generator::comment_for_glyph(std::size_t index)\n{\n    index += 32;\n    std::ostringstream s;\n    s << \"Character 0x\"\n      << std::hex << std::setfill('0') << std::setw(2) << index\n      << std::dec << \" (\" << index;\n\n    if (std::isprint(index)) {\n        s << \": '\" << static_cast<char>(index) << \"'\";\n    }\n\n    s << \")\";\n\n    return s.str();\n}\n\n}\n"
  },
  {
    "path": "lib/src/fontsourcecodegenerator.h",
    "content": "#ifndef FONTSOURCECODEGENERATOR_H\n#define FONTSOURCECODEGENERATOR_H\n\n#include \"fontdata.h\"\n#include \"sourcecode.h\"\n#include \"format.h\"\n\n#include <string>\n#include <sstream>\n#include <bitset>\n#include <algorithm>\n\nnamespace f2b\n{\n\nstatic constexpr auto byte_size = 8;\n\nstruct source_code_options\n{\n    enum bit_numbering_type { lsb, msb };\n    enum export_method_type { export_selected, export_all };\n\n    uint8_t wrap_column = 80;\n    export_method_type export_method { export_selected };\n    bit_numbering_type bit_numbering { lsb };\n    bool invert_bits { false };\n    bool include_line_spacing { false };\n    source_code::indentation indentation { source_code::tab {} };\n};\n\n/**\n * @brief Converts line margins to pixel margins.\n * @param line_margins - margins expressed in lines\n * @param glyph_size\n * @return Margins expressed in pixel offset for a given glyph size (width).\n */\nfont::margins pixel_margins(font::margins line_margins, font::glyph_size glyph_size);\n\n\nclass font_source_code_generator_interface\n{\npublic:\n    virtual std::string current_timestamp() = 0;\n    virtual std::string comment_for_glyph(std::size_t index) = 0;\n};\n\n/**\n * @brief A simple converter which converts fixed-width fonts.\n *\n * Starting from the top-left 8x8 block, it converts each block from\n * left to right, from top to down.\n *\n * For 'w' width and 'h' height, each w x h block is converted to the total\n * amount of h*(Int(w/8)+1) bytes, i.e. each row is represented by Int(w/8)+1 bytes,\n * so:\n *\n *   width | bytes\n *  ---------------\n *     5   |   1\n *     7   |   1\n *     8   |   1\n *    12   |   2\n *    16   |   2\n *    19   |   3\n *\n * The left bit is the highest bit, and the first byte in a row is a leftmost byte.\n * Unused trailing bits are zeroed.\n * Example:\n * 'A' (17 pixels wide)\n * ...######........ -> 0x1F, 0x80, 0x00\n * ...#######....... -> 0x1F, 0xC0, 0x00\n * .......###....... -> 0x01, 0xC0, 0x00\n * ......##.##...... -> 0x03, 0x60, 0x00\n * ......##.##...... -> 0x03, 0x60, 0x00\n * .....##...##..... -> 0x06, 0x30, 0x00\n * .....##...##..... -> 0x06, 0x30, 0x00\n * ....##....##..... -> 0x0C, 0x30, 0x00\n * ....#########.... -> 0x0F, 0xF8, 0x00\n * ...##########.... -> 0x1F, 0xF8, 0x00\n * ...##.......##... -> 0x18, 0x0C, 0x00\n * ..##........##... -> 0x30, 0x0C, 0x00\n * ######...#######. -> 0xFC, 0x7F, 0x00\n * ######...#######. -> 0xFC, 0x7F, 0x00\n * ................. -> 0x00, 0x00, 0x00\n *\n * This char will result in the byte sequence: 0x1F, 0x80, 0x00, 0x1F, 0xC0, 0x00, ...\n *\n * '9' (8 pixels wide)\n * ..XXXX.. -> 0x3C\n * .XX..XX. -> 0x66\n * .XX..XX. -> 0x66\n * ..XXXXX. -> 0x3E\n * .....XX. -> 0x06\n * .....XX. -> 0x06\n * .XX..XX. -> 0x66\n * ..XXXX.. -> 0x3C\n * ........ -> 0x00\n *\n * This char will result in the byte sequence: 0x3c, 0x66, 0x66, ...\n *\n */\nclass font_source_code_generator : private font_source_code_generator_interface\n{\npublic:\n\n    font_source_code_generator(source_code_options options):\n        options_ { options }\n    {}\n\n    /**\n     * A template method that generates source code for a given \\c face.\n     *\n     * The source code format is defined as a template parameter,\n     * and the optional \\c font_name parameter allows for customising\n     * the font byte array parameter.\n     *\n     * The generator builds the source code out of building blocks -\n     * structs from \\c source_code::Idiom namespace.\n     *\n     * @see source_code::Idiom\n     */\n    template<typename T>\n    std::string generate(const font::face& face, std::string font_name = \"font\");\n\nprivate:\n    template<typename T>\n    std::string generate_all(const font::face& face, std::string font_name = \"font\");\n\n    template<typename T>\n    std::string generate_subset(const font::face& face, std::string font_name = \"font\");\n\n    template<typename T, typename V>\n    std::string subset_lut(const std::set<std::size_t>& exported_glyph_ids,\n                           bool has_dummy_blank_glyph,\n                           std::size_t bytes_per_glyph);\n\n    template<typename T>\n    void output_glyph(const font::glyph& glyph, font::glyph_size size, font::margins margins, std::ostream& s);\n\n\n    std::string current_timestamp() override;\n    std::string comment_for_glyph(std::size_t index) override;\n    source_code_options options_;\n};\n\ntemplate<typename T>\nvoid font_source_code_generator::output_glyph(const font::glyph& glyph, font::glyph_size size, font::margins margins, std::ostream& s)\n{\n    using namespace source_code;\n    std::bitset<byte_size> bits;\n    std::size_t bit_pos { 0 };\n    std::size_t col { 0 };\n\n    auto append_byte = [&](std::bitset<byte_size>& bits, std::ios::pos_type& pos) {\n        if (options_.invert_bits) {\n            bits.flip();\n        }\n        auto byte = static_cast<uint8_t>(bits.to_ulong());\n        s << idiom::value<T, uint8_t> { std::move(byte) };\n        bits.reset();\n\n        if (s.tellp() - pos >= options_.wrap_column) {\n            s << idiom::array_line_break<T, uint8_t> {};\n            pos = s.tellp();\n            s << idiom::begin_array_row<T, uint8_t> { options_.indentation };\n        }\n    };\n\n\n    auto pos = s.tellp();\n    s << idiom::begin_array_row<T, uint8_t> { options_.indentation };\n\n    std::for_each(glyph.pixels().cbegin() + margins.top, glyph.pixels().cend() - margins.bottom,\n                  [&](auto pixel) {\n        switch (options_.bit_numbering) {\n        case source_code_options::lsb:\n            bits[bit_pos] = pixel;\n            break;\n        case source_code_options::msb:\n            bits[byte_size-1-bit_pos] = pixel;\n            break;\n        }\n\n        ++bit_pos;\n        ++col;\n\n        if (col >= size.width) {\n            append_byte(bits, pos);\n            bit_pos = 0;\n            col = 0;\n        } else if (bit_pos >= byte_size) {\n            append_byte(bits, pos);\n            bit_pos = 0;\n        }\n    });\n}\n\ntemplate<typename T>\nstd::string font_source_code_generator::generate_all(const font::face& face, std::string font_name)\n{\n    using namespace source_code;\n\n    auto [size, margins] = [&] () -> std::pair<font::glyph_size, font::margins> {\n        if (options_.include_line_spacing) {\n            return { face.glyphs_size(), {} };\n        }\n        auto line_margins = face.calculate_margins();\n        return { face.glyphs_size().with_margins(line_margins),\n                    pixel_margins(line_margins, face.glyphs_size()) };\n    }();\n\n    std::ostringstream s;\n    s << idiom::begin<T> { font_name, size, current_timestamp() } << std::endl;\n\n    s << idiom::comment<T> {} << std::endl;\n    s << idiom::comment<T> { \"Pseudocode for retrieving data for a specific character:\" } << std::endl;\n    s << idiom::comment<T> {} << std::endl;\n    s << idiom::comment<T> { \"bytes_per_char = font_height * (font_width / 8 + ((font_width % 8) ? 1 : 0))\" } << std::endl;\n    s << idiom::comment<T> { \"offset = (ascii_code(character) - ascii_code(' ')) * bytes_per_char\" } << std::endl;\n    s << idiom::comment<T> { \"data = \" + font_name + \"[offset]\" } << std::endl;\n    s << idiom::comment<T> {};\n\n    s << idiom::begin_array<T, uint8_t> { std::move(font_name) };\n\n    std::size_t glyph_id { 0 };\n    for (const auto& glyph : face.glyphs()) {\n        output_glyph<T>(glyph, size, margins, s);\n        s << idiom::comment<T, uint8_t> { comment_for_glyph(glyph_id) };\n        s << idiom::array_line_break<T, uint8_t> {};\n        ++glyph_id;\n    }\n\n    s << idiom::end_array<T, uint8_t> {};\n    s << idiom::end<T> {};\n\n    return s.str();\n}\n\ntemplate<typename T, typename V>\nstd::string font_source_code_generator::subset_lut(const std::set<std::size_t>& exported_glyph_ids,\n                                                   bool has_dummy_blank_glyph,\n                                                   std::size_t bytes_per_glyph)\n{\n    using namespace source_code;\n\n    std::ostringstream s;\n\n    // If there's a dummy blank glyph, first exported character is at index 1, not 0.\n    V exported_id { static_cast<V>(has_dummy_blank_glyph) };\n\n    auto last_exported_glyph = std::prev(exported_glyph_ids.end());\n\n    s << idiom::begin_array<T, V> { \"lut\" };\n\n    // Control line breaks with this flag - add a line break only before an exported glyph\n    bool is_previous_exported = true;\n    for (std::size_t glyph_id = 0; glyph_id <= *last_exported_glyph; ++glyph_id) {\n        if (exported_glyph_ids.find(glyph_id) != exported_glyph_ids.end()) {\n            if (!is_previous_exported)\n                s << idiom::array_line_break<T, V> {};\n            s << idiom::begin_array_row<T, V> { options_.indentation };\n            s << idiom::value<T, V> { static_cast<V>(bytes_per_glyph * exported_id) };\n            s << idiom::comment<T, V> { comment_for_glyph(glyph_id) };\n            ++exported_id;\n            s << idiom::array_line_break<T, V> {};\n            is_previous_exported = true;\n        } else {\n            if (is_previous_exported)\n                s << idiom::begin_array_row<T, V> { options_.indentation };\n            s << idiom::value<T, V> { 0 };\n            is_previous_exported = false;\n        }\n    }\n\n    s << idiom::end_array<T, V> {};\n\n    return s.str();\n}\n\ntemplate<typename T>\nstd::string font_source_code_generator::generate_subset(const font::face& face, std::string font_name)\n{\n    using namespace source_code;\n\n    auto [size, margins] = [&] () -> std::pair<font::glyph_size, font::margins> {\n        if (options_.include_line_spacing) {\n            return { face.glyphs_size(), {} };\n        }\n        auto line_margins = face.calculate_margins();\n        return { face.glyphs_size().with_margins(line_margins),\n                    pixel_margins(line_margins, face.glyphs_size()) };\n    }();\n\n    std::ostringstream s;\n    s << idiom::begin<T> { font_name, size, current_timestamp() } << std::endl;\n\n    s << idiom::comment<T> {} << std::endl;\n    s << idiom::comment<T> { \"Pseudocode for retrieving data for a specific character:\" } << std::endl;\n    s << idiom::comment<T> {} << std::endl;\n    s << idiom::comment<T> { \"offset = ascii_code(character) - ascii_code(' ')\" } << std::endl;\n    s << idiom::comment<T> { \"data = \" + font_name + \"[lut[offset]]\" } << std::endl;\n    s << idiom::comment<T> {};\n\n    s << idiom::begin_array<T, uint8_t> { std::move(font_name) };\n\n    // Not exported characters are replaced with a space character.\n    // If space character (ASCII 32, the first glyph) itself is not exported,\n    // we add a dummy blank character and default all not exported characters to it.\n    bool has_dummy_blank_glyph = false;\n    if (face.exported_glyph_ids().find(0) == face.exported_glyph_ids().end()) {\n        output_glyph<T>(font::glyph(face.glyphs_size()), size, margins, s);\n        s << idiom::comment<T, uint8_t> { \"Dummy blank character\" };\n        s << idiom::array_line_break<T, uint8_t> {};\n        has_dummy_blank_glyph = true;\n    }\n\n    for (auto glyph_id : face.exported_glyph_ids()) {\n        const auto& glyph = face.glyph_at(glyph_id);\n        output_glyph<T>(glyph, size, margins, s);\n        s << idiom::comment<T, uint8_t> { comment_for_glyph(glyph_id) };\n        s << idiom::array_line_break<T, uint8_t> {};\n    }\n\n    s << idiom::end_array<T, uint8_t> {};\n\n\n    auto bytes_per_line = size.width / byte_size + (size.width % byte_size ? 1 : 0);\n    auto bytes_per_glyph = size.height * bytes_per_line;\n\n    auto max_offset = (face.exported_glyph_ids().size() - 1) * bytes_per_glyph;\n\n    if (max_offset < (1<<8)) {\n        s << subset_lut<T,uint8_t>(face.exported_glyph_ids(), has_dummy_blank_glyph, bytes_per_glyph);\n    } else if (max_offset < (1<<16)) {\n        s << subset_lut<T,uint16_t>(face.exported_glyph_ids(), has_dummy_blank_glyph, bytes_per_glyph);\n    } else if (max_offset < (1ull<<32)) {\n        s << subset_lut<T,uint32_t>(face.exported_glyph_ids(), has_dummy_blank_glyph, bytes_per_glyph);\n    } else {\n        s << subset_lut<T,uint64_t>(face.exported_glyph_ids(), has_dummy_blank_glyph, bytes_per_glyph);\n    }\n\n    s << idiom::end<T> {};\n\n    return s.str();\n}\n\ntemplate<typename T>\nstd::string font_source_code_generator::generate(const font::face &face, std::string font_name)\n{\n    switch (options_.export_method) {\n    case source_code_options::export_all:\n        return generate_all<T>(face, font_name);\n    case source_code_options::export_selected:\n        return generate_subset<T>(face, font_name);\n    }\n}\n\n} // namespace f2b\n\n\n#endif // FONTSOURCECODEGENERATOR_H\n"
  },
  {
    "path": "lib/src/format.h",
    "content": "#ifndef FORMAT_H\n#define FORMAT_H\n\n#include <string>\n#include <iostream>\n#include <iomanip>\n#include \"sourcecode.h\"\n\nnamespace f2b\n{\n\n/// This namespace defines available source code formats\nnamespace format\n{\n\nusing namespace source_code;\n\n/// A type that identifies C-based language format.\nstruct c_based {};\n/// A type that identifies Python language format.\nstruct python {};\n\n/// C-style code (suitable also for C++)\nstruct c\n{\n    using lang = c_based;\n    static constexpr std::string_view identifier = \"c\";\n};\n\n/// Arduino-flavoured C-style code\nstruct arduino\n{\n    using lang = c_based;\n    static constexpr std::string_view identifier = \"arduino\";\n};\n\n/// Python code format for List object\nstruct python_list\n{\n    using lang = python;\n    static constexpr std::string_view identifier = \"python-list\";\n};\n\n/// Python code format for Bytes object\nstruct python_bytes\n{\n    using lang = python;\n    static constexpr std::string_view identifier = \"python-bytes\";\n};\n\n\nconstexpr auto available_formats = {\n    c::identifier,\n    arduino::identifier,\n    python_list::identifier,\n    python_bytes::identifier\n};\n\n} // namespace Format\n\n/// Type trait that is true for C-based language formats\ntemplate<typename T, typename = void>\nstruct is_c_based : std::false_type {};\ntemplate<typename T>\nstruct is_c_based<T, std::enable_if_t<std::is_same<typename T::lang, f2b::format::c_based>::value>> : std::true_type {};\n\n/// Type trait that is true for Python language formats\ntemplate<typename T, typename = void>\nstruct is_python : std::false_type {};\ntemplate<typename T>\nstruct is_python<T, std::enable_if_t<std::is_same<typename T::lang, format::python>::value>> : std::true_type {};\n\nstatic_assert (is_c_based<format::c>::value, \"***\");\nstatic_assert (is_c_based<format::arduino>::value, \"***\");\nstatic_assert (!is_c_based<format::python_list>::value, \"***\");\nstatic_assert (!is_c_based<format::python_bytes>::value, \"***\");\n\nstatic_assert (!is_python<format::c>::value, \"***\");\nstatic_assert (!is_python<format::arduino>::value, \"***\");\nstatic_assert (is_python<format::python_list>::value, \"***\");\nstatic_assert (is_python<format::python_bytes>::value, \"***\");\n\n\n//\n// The code below defines operator<< for all structs from the\n// source_code::idiom namespace. These functions use type traits\n// and compile-time ifs and to improve source code generation\n// performance.\n//\n\ntemplate<typename T, typename V, typename = void>\nstruct is_bytearray : std::false_type {};\ntemplate<typename T, typename V>\nstruct is_bytearray<T, V, std::enable_if_t<std::is_same<T, format::python_bytes>::value && std::is_same<V, uint8_t>::value>> : std::true_type {};\n\nstatic_assert (!is_bytearray<format::c, uint8_t>::value, \"***\");\nstatic_assert (!is_bytearray<format::python_bytes, void>::value, \"***\");\nstatic_assert (!is_bytearray<format::python_bytes, int32_t>::value, \"***\");\nstatic_assert (!is_bytearray<format::python_bytes, std::string>::value, \"***\");\nstatic_assert (is_bytearray<format::python_bytes, uint8_t>::value, \"***\");\n\n\n// Begin\n\ntemplate<typename T>\ninline std::ostream& operator<<(std::ostream& s, source_code::idiom::begin<T> b)\n{\n    if constexpr (is_c_based<T>::value) {\n        s << \"//\\n// \" << b.font_name << \"\\n\"\n          << \"// Font Size: \" << b.font_size.width << \"x\" << b.font_size.height << \"px\\n\"\n          << \"// Created: \" << b.timestamp << \"\\n//\\n\";\n        if constexpr (std::is_same<T, format::arduino>::value) {\n            s << \"\\n#include <Arduino.h>\\n\";\n        } else {\n            s << \"\\n#include <stdint.h>\\n\";\n        }\n    } else if constexpr (is_python<T>::value) {\n        s << \"#\\n# \" << b.font_name << \"\\n\"\n          << \"# Font Size: \" << b.font_size.width << \"x\" << b.font_size.height << \"px\\n\"\n          << \"# Created: \" << b.timestamp << \"\\n#\\n\";\n    }\n    return s;\n}\n\n\n// Constant\n\ntemplate<typename T, typename V>\ninline std::ostream& operator<<(std::ostream& s, source_code::idiom::constant<T, V> c)\n{\n    using namespace source_code::idiom;\n\n    if constexpr (std::is_same<T, format::c>::value) {\n\n        if constexpr (std::is_same<V, uint8_t>::value) {\n            s << \"\\n\\nconst unsigned char \";\n        } else if constexpr (std::is_same<V, int8_t>::value) {\n            s << \"\\n\\nconst int8_t \";\n        } else if constexpr (std::is_same<V, int16_t>::value) {\n            s << \"\\n\\nconst int16_t \";\n        } else if constexpr (std::is_same<V, int32_t>::value) {\n            s << \"\\n\\nconst int32_t \";\n        }\n\n        s << c.name << \" = \" << c.value << \";\\n\";\n\n    } else if constexpr (std::is_same<T, format::arduino>::value) {\n\n        if constexpr (std::is_same<V, uint8_t>::value) {\n            s << \"\\n\\nconst uint8_t \";\n        } else if constexpr (std::is_same<V, int8_t>::value) {\n            s << \"\\n\\nconst int8_t \";\n        } else if constexpr (std::is_same<V, int16_t>::value) {\n            s << \"\\n\\nconst int16_t \";\n        } else if constexpr (std::is_same<V, int32_t>::value) {\n            s << \"\\n\\nconst int32_t \";\n        }\n\n        s << c.name << \" PROGMEM = \" << c.value << \";\\n\";\n\n    } else if constexpr (is_python<T>::value) {\n\n        s << \"\\n\\n\" << c.name << \" = \" << c.value << \"\\n\";\n\n    }\n    return s;\n}\n\n\n// BeginArray\n\ntemplate<typename T, typename V>\ninline std::ostream& operator<<(std::ostream& s, source_code::idiom::begin_array<T, V> b)\n{\n    using namespace source_code::idiom;\n\n    if constexpr (std::is_same<T, format::c>::value) {\n\n        if constexpr (std::is_same<V, uint8_t>::value) {\n            s << \"\\n\\nconst unsigned char \";\n        } else if constexpr (std::is_same<V, uint16_t>::value) {\n            s << \"\\n\\nconst uint16_t \";\n        } else if constexpr (std::is_same<V, uint32_t>::value) {\n            s << \"\\n\\nconst uint32_t \";\n        } else if constexpr (std::is_same<V, uint64_t>::value) {\n            s << \"\\n\\nconst uint64_t \";\n        } else if constexpr (std::is_same<V, int8_t>::value) {\n            s << \"\\n\\nconst int8_t \";\n        } else if constexpr (std::is_same<V, int16_t>::value) {\n            s << \"\\n\\nconst int16_t \";\n        } else if constexpr (std::is_same<V, int32_t>::value) {\n            s << \"\\n\\nconst int32_t \";\n        } else if constexpr (std::is_same<V, int64_t>::value) {\n            s << \"\\n\\nconst int64_t \";\n        }\n\n        s << b.array_name << \"[] = {\\n\";\n\n    } else if constexpr (std::is_same<T, format::arduino>::value) {\n\n        if constexpr (std::is_same<V, uint8_t>::value) {\n            s << \"\\n\\nconst uint8_t \";\n        } else if constexpr (std::is_same<V, uint16_t>::value) {\n            s << \"\\n\\nconst uint16_t \";\n        } else if constexpr (std::is_same<V, uint32_t>::value) {\n            s << \"\\n\\nconst uint32_t \";\n        } else if constexpr (std::is_same<V, uint64_t>::value) {\n            s << \"\\n\\nconst uint64_t \";\n        } else if constexpr (std::is_same<V, int8_t>::value) {\n            s << \"\\n\\nconst int8_t \";\n        } else if constexpr (std::is_same<V, int16_t>::value) {\n            s << \"\\n\\nconst int16_t \";\n        } else if constexpr (std::is_same<V, int32_t>::value) {\n            s << \"\\n\\nconst int32_t \";\n        } else if constexpr (std::is_same<V, int64_t>::value) {\n            s << \"\\n\\nconst int64_t \";\n        }\n\n        s << b.array_name << \"[] PROGMEM = {\\n\";\n\n    } else if constexpr (std::is_same<T, format::python_list>::value) {\n\n        s << \"\\n\\n\" << b.array_name << \" = [\\n\";\n\n    } else if constexpr (std::is_same<T, format::python_bytes>::value) {\n\n        if constexpr (std::is_same<V, uint8_t>::value) {\n            s << \"\\n\\n\" << b.array_name << \" = b'' \\\\\\n\";\n        } else {\n            s << \"\\n\\n\" << b.array_name << \" = [\\n\";\n        }\n    }\n    return s;\n}\n\n\n// BeginArrayRow\n\ntemplate<typename T, typename V>\ninline std::ostream& operator<<(std::ostream& s, source_code::idiom::begin_array_row<T, V> b)\n{\n    using namespace source_code::idiom;\n\n    if constexpr (std::is_same<T, format::python_bytes>::value && std::is_same<V, uint8_t>::value) {\n        s << b.tab << \"b'\";\n    } else {\n        s << b.tab;\n    }\n    return s;\n}\n\n\n// Value\n\ntemplate<typename T, typename V>\ninline std::ostream& operator<<(std::ostream& s, source_code::idiom::value<T, V> v)\n{\n    if constexpr (std::is_same<V, uint8_t>::value) {\n        if constexpr (std::is_same<T, format::python_bytes>::value) {\n            s << \"\\\\x\"\n              << std::setw(2)\n              << std::setfill('0')\n              << std::uppercase\n              << std::hex\n              << static_cast<unsigned>(v.value);\n        } else {\n            s << \"0x\"\n              << std::setw(2)\n              << std::setfill('0')\n              << std::uppercase\n              << std::hex\n              << static_cast<unsigned>(v.value)\n              << \",\";\n        }\n    } else {\n        s << std::resetiosflags(std::ios_base::basefield) << v.value << \",\";\n    }\n    return s;\n}\n\n\n// Comment\n\ntemplate<typename T, typename V>\ninline std::ostream& operator<<(std::ostream& s, source_code::idiom::comment<T, V> b)\n{\n    if constexpr (is_c_based<T>::value) {\n        if constexpr (!std::is_void<V>::value) { // add a space if a comment is inside an array\n            s << \" \";\n        }\n        s << \"// \" << b.comment;\n    } else if constexpr (is_python<T>::value && !is_bytearray<T,V>::value) {\n        if constexpr (!std::is_void<V>::value) {\n            s << \" \";\n        }\n        s << \"# \" << b.comment;\n    }\n    return s;\n}\n\n\n// LineBreak\n\ntemplate<typename T, typename V>\ninline std::ostream& operator<<(std::ostream& s, source_code::idiom::array_line_break<T, V>)\n{\n    if constexpr (is_bytearray<T,V>::value) {\n        s << \"' \\\\\\n\";\n    } else {\n        s << \"\\n\";\n    }\n    return s;\n}\n\n\n// EndArray\n\ntemplate<typename T, typename V>\ninline std::ostream& operator<<(std::ostream& s, source_code::idiom::end_array<T, V>)\n{\n    if constexpr (is_c_based<T>::value) {\n        s << \"};\\n\";\n    } else {\n        if constexpr (std::is_same<T, format::python_list>::value || !is_bytearray<T,V>::value) {\n            s << \"]\\n\";\n        }\n    }\n    return s;\n}\n\n\n// End\n\ntemplate<typename T>\ninline std::ostream& operator<<(std::ostream& s, source_code::idiom::end<T>)\n{\n    s << \"\\n\\n\";\n    return s;\n}\n\n} // namespace f2b\n\n\n#endif // FORMAT_H\n"
  },
  {
    "path": "lib/src/include/f2b.h",
    "content": "#ifndef F2B_H\n#define F2B_H\n\n#include \"f2b/f2b.h\"\n\n#endif // F2B_H\n"
  },
  {
    "path": "lib/src/sourcecode.h",
    "content": "#ifndef SOURCECODE_H\n#define SOURCECODE_H\n\n#include \"fontdata.h\"\n#include <string>\n#include <iostream>\n#include <variant>\n\nnamespace f2b  {\n\nnamespace source_code {\n\n/// A struct representing a Tabulation character\nstruct tab {};\n\n/// A struct representing a repeated space character (used instead of tabulator)\nstruct space { std::size_t num_spaces; };\n\n/// Indentation can be either a Tab, or multiple Spaces.\nusing indentation = std::variant<tab,space>;\n\n/**\n * This namespace gathers building blocks for a source code generator:\n * - begin (source code file)\n * - begin array\n * - begin array row\n * - constant definition\n * - value (e.g. byte)\n * - comment\n * - line break with an array\n * - end array\n * - end (source code file).\n *\n * All the structs in this namespace are templates taking Source Code Format\n * as a template parameter. They also take parameters as necessary (e.g.\n * \\c array_name for \\c BeginArray).\n * All the structs define the \\c operator<< which outputs the relevant source\n * code idiom to the given output stream. \\c FontSourceCodeGenerator uses them\n * with an \\c std::stringstream to output the resulting source code of the font face.\n */\n\nnamespace idiom {\n\ntemplate<typename T>\nstruct begin {\n    std::string font_name;\n    font::glyph_size font_size;\n    std::string timestamp;\n};\n\ntemplate<typename T, typename V>\nstruct constant {\n    std::string name;\n    V value;\n};\n\ntemplate<typename T, typename V>\nstruct begin_array {\n    std::string array_name;\n};\n\ntemplate<typename T, typename V>\nstruct begin_array_row {\n    indentation tab;\n};\n\ntemplate<typename T, typename V>\nstruct value {\n    V value;\n};\n\ntemplate<typename T, typename V = void>\nstruct comment {\n    std::string comment;\n};\n\ntemplate<typename T, typename V = void>\nstruct array_line_break {};\n\ntemplate<typename T, typename V = void>\nstruct end_array {};\n\ntemplate<typename T>\nstruct end {};\n\n} // namespace Idiom\n\ninline bool operator==(const indentation& lhs, const indentation& rhs) {\n    if (std::holds_alternative<tab>(lhs) && std::holds_alternative<tab>(rhs)) {\n        return true;\n    }\n    if (std::holds_alternative<space>(lhs) && std::holds_alternative<space>(rhs)) {\n        return std::get<space>(lhs).num_spaces == std::get<space>(rhs).num_spaces;\n    }\n    return false;\n}\n\ninline std::ostream& operator<<(std::ostream& o, const indentation& t) {\n    if (std::holds_alternative<space>(t)) {\n        o << std::string(std::get<space>(t).num_spaces, ' ');\n    } else {\n        o << \"\\t\";\n    }\n    return o;\n}\n\n} // namespace SourceCode\n} // namespace f2b\n\n#endif // SOURCECODE_H\n"
  },
  {
    "path": "lib/test/CMakeLists.txt",
    "content": "\ncmake_minimum_required(VERSION 3.9)\n\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\nset(UNIT_TEST_LIST\n    fontface\n    glyph\n    sourcecode)\n\nforeach(NAME IN LISTS UNIT_TEST_LIST)\n    list(APPEND UNIT_TEST_SOURCE_LIST ${NAME}_test.cpp)\nendforeach()\n\nset(TARGET_NAME font2bytes_tests)\n\nadd_executable(${TARGET_NAME}\n    ${UNIT_TEST_SOURCE_LIST})\n\ntarget_link_libraries(${TARGET_NAME} PUBLIC font2bytes gtest_main)\n\ntarget_include_directories(${TARGET_NAME}\n    PUBLIC ../src)\n\nadd_test(NAME ${TARGET_NAME} COMMAND ${TARGET_NAME})\n"
  },
  {
    "path": "lib/test/fontface_test.cpp",
    "content": "#include \"gtest/gtest.h\"\n#include \"fontdata.h\"\n\nusing namespace f2b;\n\nclass TestFaceData : public font::face_reader\n{\npublic:\n    font::glyph_size font_size() const override { return { 4, 3 }; }\n    std::size_t num_glyphs() const override { return 5; }\n\n    bool is_pixel_set(std::size_t glyph_id, font::point p) const override\n    {\n        auto glyph_offset = glyph_id * font_size().width * font_size().height;\n        return pixels[glyph_offset + p.offset(font_size())];\n    }\n\n    std::vector<bool> pixels\n    {\n        0, 0, 0, 0,\n        0, 0, 0, 1,\n        0, 0, 1, 0,\n        //\n        0, 1, 0, 0,\n        1, 0, 1, 0,\n        1, 0, 0, 1,\n        //\n        1, 1, 1, 1,\n        1, 1, 0, 1,\n        1, 0, 1, 1,\n        //\n        1, 0, 0, 1,\n        1, 0, 1, 0,\n        0, 0, 1, 1,\n        //\n        0, 1, 1, 1,\n        1, 1, 0, 0,\n        1, 0, 1, 0\n    };\n};\n\n\nTEST(FaceTest, Initialization)\n{\n    TestFaceData test_data;\n    font::face face(test_data);\n\n    EXPECT_EQ(5, face.num_glyphs());\n\n    auto g = face.glyph_at(2);\n    EXPECT_EQ(test_data.font_size(), g.size());\n\n    for (std::size_t i = 0; i < face.num_glyphs(); i++) {\n        auto g = face.glyph_at(i);\n        auto g_offset = i * test_data.font_size().width * test_data.font_size().height;\n\n        for (std::size_t y = 0; y < test_data.font_size().height; y++) {\n            for (std::size_t x = 0; x < test_data.font_size().width; x++) {\n\n                font::point p { x, y };\n                auto offset = g_offset + p.offset(test_data.font_size());\n                EXPECT_EQ(bool(test_data.pixels[offset]), g.is_pixel_set(p))\n                        << \"glyph: \" << i << \" x: \" << x << \" y: \" << y;\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "lib/test/glyph_test.cpp",
    "content": "#include \"gtest/gtest.h\"\n#include \"fontdata.h\"\n\nusing namespace f2b;\n\nTEST(GlyphTest, API)\n{\n    font::glyph_size sz { 5, 5 };\n\n    EXPECT_EQ(sz, font::glyph(sz).size());\n}\n"
  },
  {
    "path": "lib/test/sourcecode_test.cpp",
    "content": "#include \"gtest/gtest.h\"\n\n\nTEST(SourceCodeTest, IdiomTraits)\n{\n}\n"
  },
  {
    "path": "test/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.9)\n\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\nfind_package(Qt5 COMPONENTS Core Gui Widgets REQUIRED)\n\nset(UNIT_TESTS\n    f2b_qt_compat_test.cpp\n    sourcecodegeneration_test.cpp\n    )\n\nset(TARGET_NAME fontedit_app_tests)\n\nadd_executable(${TARGET_NAME}\n    ${UNIT_TESTS})\n\ntarget_link_libraries(${TARGET_NAME} PUBLIC Qt5::Widgets Qt5::Core appbundle gtest_main)\ntarget_compile_definitions(${TARGET_NAME} PRIVATE ASSETS_DIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/assets\\\")\n\nadd_test(NAME ${TARGET_NAME} COMMAND ${TARGET_NAME})\n"
  },
  {
    "path": "test/assets/monaco8-subset.c-test",
    "content": "//\n// font\n// Font Size: 5x8px\n// Created: <timestamp>\n//\n\n#include <stdint.h>\n\n// \n// Pseudocode for retrieving data for a specific character:\n// \n// offset = ascii_code(character) - ascii_code(' ')\n// data = font[lut[offset]]\n// \n\nconst unsigned char font[] = {\n    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Character 0x20 (32: ' ')\n    0x04,0x04,0x04,0x04,0x04,0x00,0x04,0x00, // Character 0x21 (33: '!')\n    0x0A,0x0A,0x00,0x00,0x00,0x00,0x00,0x00, // Character 0x22 (34: '\"')\n    0x00,0x08,0x0E,0x0A,0x0F,0x04,0x00,0x00, // Character 0x23 (35: '#')\n    0x04,0x0E,0x06,0x04,0x0C,0x0C,0x0E,0x00, // Character 0x24 (36: '$')\n    0x12,0x09,0x05,0x0C,0x16,0x14,0x01,0x00, // Character 0x25 (37: '%')\n    0x06,0x05,0x06,0x02,0x0D,0x09,0x06,0x00, // Character 0x26 (38: '&')\n    0x04,0x04,0x00,0x00,0x00,0x00,0x00,0x00, // Character 0x27 (39: ''')\n    0x08,0x04,0x02,0x02,0x02,0x02,0x04,0x08, // Character 0x28 (40: '(')\n    0x02,0x04,0x08,0x08,0x08,0x08,0x04,0x02, // Character 0x29 (41: ')')\n    0x04,0x0E,0x0E,0x04,0x00,0x00,0x00,0x00, // Character 0x2a (42: '*')\n    0x00,0x00,0x04,0x04,0x0F,0x04,0x00,0x00, // Character 0x2b (43: '+')\n    0x00,0x00,0x00,0x00,0x00,0x04,0x04,0x04, // Character 0x2c (44: ',')\n    0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00, // Character 0x2d (45: '-')\n    0x00,0x00,0x00,0x00,0x00,0x04,0x04,0x00, // Character 0x2e (46: '.')\n    0x00,0x08,0x00,0x04,0x02,0x02,0x01,0x00, // Character 0x2f (47: '/')\n    0x06,0x0A,0x09,0x15,0x0B,0x0A,0x04,0x00, // Character 0x30 (48: '0')\n    0x04,0x06,0x04,0x04,0x04,0x04,0x00,0x00, // Character 0x31 (49: '1')\n    0x0E,0x08,0x08,0x04,0x02,0x02,0x00,0x00, // Character 0x32 (50: '2')\n    0x06,0x08,0x0C,0x0C,0x08,0x08,0x06,0x00, // Character 0x33 (51: '3')\n    0x00,0x0C,0x0A,0x08,0x0F,0x08,0x00,0x00, // Character 0x34 (52: '4')\n    0x0E,0x02,0x02,0x0C,0x08,0x08,0x06,0x00, // Character 0x35 (53: '5')\n    0x0C,0x02,0x00,0x0B,0x12,0x0A,0x04,0x00, // Character 0x36 (54: '6')\n    0x0E,0x08,0x08,0x04,0x00,0x02,0x00,0x00, // Character 0x37 (55: '7')\n    0x0C,0x0A,0x0E,0x0E,0x0A,0x0A,0x04,0x00, // Character 0x38 (56: '8')\n    0x06,0x08,0x09,0x0E,0x08,0x08,0x06,0x00, // Character 0x39 (57: '9')\n    0x00,0x00,0x04,0x00,0x00,0x04,0x04,0x00, // Character 0x3a (58: ':')\n    0x00,0x00,0x04,0x00,0x00,0x04,0x04,0x04, // Character 0x3b (59: ';')\n    0x00,0x00,0x00,0x04,0x02,0x0C,0x00,0x00, // Character 0x3c (60: '<')\n    0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00, // Character 0x3d (61: '=')\n    0x00,0x00,0x01,0x06,0x08,0x02,0x00,0x00, // Character 0x3e (62: '>')\n    0x06,0x08,0x08,0x04,0x00,0x00,0x00,0x00, // Character 0x3f (63: '?')\n    0x06,0x00,0x17,0x13,0x0F,0x00,0x04,0x00, // Character 0x40 (64: '@')\n    0x04,0x04,0x0E,0x0A,0x0E,0x09,0x00,0x00, // Character 0x41 (65: 'A')\n    0x06,0x0A,0x0A,0x06,0x0A,0x0A,0x00,0x00, // Character 0x42 (66: 'B')\n    0x0C,0x02,0x00,0x01,0x02,0x02,0x0C,0x00, // Character 0x43 (67: 'C')\n    0x06,0x08,0x00,0x00,0x08,0x0A,0x00,0x00, // Character 0x44 (68: 'D')\n    0x0E,0x02,0x02,0x0E,0x02,0x02,0x00,0x00, // Character 0x45 (69: 'E')\n    0x0E,0x02,0x02,0x0E,0x02,0x02,0x00,0x00, // Character 0x46 (70: 'F')\n    0x0C,0x02,0x00,0x09,0x0A,0x0A,0x0C,0x00, // Character 0x47 (71: 'G')\n    0x00,0x09,0x09,0x0F,0x09,0x09,0x00,0x00, // Character 0x48 (72: 'H')\n    0x0E,0x04,0x04,0x04,0x04,0x04,0x00,0x00, // Character 0x49 (73: 'I')\n    0x0C,0x08,0x08,0x08,0x08,0x08,0x06,0x00, // Character 0x4a (74: 'J')\n    0x00,0x08,0x06,0x06,0x04,0x08,0x00,0x00, // Character 0x4b (75: 'K')\n    0x00,0x02,0x02,0x02,0x02,0x02,0x00,0x00, // Character 0x4c (76: 'L')\n    0x01,0x19,0x1B,0x1F,0x15,0x11,0x00,0x00, // Character 0x4d (77: 'M')\n    0x00,0x03,0x03,0x05,0x0D,0x09,0x00,0x00, // Character 0x4e (78: 'N')\n    0x06,0x0A,0x09,0x11,0x09,0x0A,0x04,0x00, // Character 0x4f (79: 'O')\n    0x06,0x0A,0x0A,0x0E,0x02,0x02,0x00,0x00, // Character 0x50 (80: 'P')\n    0x06,0x0A,0x09,0x11,0x09,0x0A,0x04,0x08, // Character 0x51 (81: 'Q')\n    0x06,0x08,0x08,0x06,0x0C,0x08,0x00,0x00, // Character 0x52 (82: 'R')\n    0x0C,0x02,0x02,0x0C,0x08,0x08,0x06,0x00, // Character 0x53 (83: 'S')\n    0x0F,0x04,0x04,0x04,0x04,0x04,0x00,0x00, // Character 0x54 (84: 'T')\n    0x00,0x01,0x01,0x01,0x09,0x0A,0x04,0x00, // Character 0x55 (85: 'U')\n    0x01,0x01,0x0A,0x0A,0x06,0x04,0x00,0x00, // Character 0x56 (86: 'V')\n    0x05,0x15,0x07,0x0F,0x0A,0x0A,0x00,0x00, // Character 0x57 (87: 'W')\n    0x00,0x0A,0x06,0x04,0x0E,0x0A,0x00,0x00, // Character 0x58 (88: 'X')\n    0x00,0x0A,0x0E,0x04,0x04,0x04,0x00,0x00, // Character 0x59 (89: 'Y')\n    0x0E,0x08,0x04,0x04,0x02,0x02,0x00,0x00, // Character 0x5a (90: 'Z')\n    0x0E,0x02,0x02,0x02,0x02,0x02,0x02,0x0E, // Character 0x5b (91: '[')\n    0x01,0x02,0x02,0x04,0x00,0x08,0x00,0x00, // Character 0x5c (92: '\\')\n    0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x07, // Character 0x5d (93: ']')\n    0x00,0x04,0x0A,0x0A,0x00,0x00,0x00,0x00, // Character 0x5e (94: '^')\n    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E, // Character 0x5f (95: '_')\n    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Character 0x60 (96: '`')\n    0x00,0x00,0x0E,0x0A,0x09,0x0A,0x00,0x00, // Character 0x61 (97: 'a')\n    0x00,0x00,0x0E,0x0A,0x08,0x08,0x02,0x00, // Character 0x62 (98: 'b')\n    0x00,0x00,0x0C,0x02,0x02,0x02,0x0C,0x00, // Character 0x63 (99: 'c')\n    0x00,0x08,0x0E,0x0A,0x09,0x0A,0x00,0x00, // Character 0x64 (100: 'd')\n    0x00,0x00,0x0E,0x0A,0x02,0x02,0x04,0x00, // Character 0x65 (101: 'e')\n    0x0C,0x04,0x0E,0x00,0x00,0x00,0x00,0x00, // Character 0x66 (102: 'f')\n    0x00,0x00,0x0E,0x0A,0x09,0x0A,0x08,0x0A, // Character 0x67 (103: 'g')\n    0x00,0x00,0x0E,0x0A,0x08,0x08,0x00,0x00, // Character 0x68 (104: 'h')\n    0x04,0x00,0x06,0x04,0x04,0x04,0x00,0x00, // Character 0x69 (105: 'i')\n    0x08,0x00,0x0E,0x08,0x08,0x08,0x08,0x04, // Character 0x6a (106: 'j')\n    0x02,0x02,0x0A,0x06,0x06,0x0A,0x00,0x00, // Character 0x6b (107: 'k')\n    0x02,0x04,0x04,0x04,0x04,0x04,0x00,0x00, // Character 0x6c (108: 'l')\n    0x00,0x00,0x0F,0x15,0x15,0x15,0x00,0x00, // Character 0x6d (109: 'm')\n    0x00,0x00,0x0E,0x0A,0x08,0x08,0x00,0x00, // Character 0x6e (110: 'n')\n    0x00,0x00,0x0E,0x08,0x01,0x0A,0x04,0x00, // Character 0x6f (111: 'o')\n    0x00,0x00,0x0E,0x0A,0x08,0x08,0x02,0x00, // Character 0x70 (112: 'p')\n    0x00,0x00,0x0E,0x0A,0x09,0x0A,0x08,0x08, // Character 0x71 (113: 'q')\n    0x00,0x00,0x0E,0x02,0x02,0x02,0x00,0x00, // Character 0x72 (114: 'r')\n    0x00,0x00,0x0E,0x02,0x0C,0x08,0x04,0x00, // Character 0x73 (115: 's')\n    0x00,0x00,0x06,0x02,0x02,0x02,0x0C,0x00, // Character 0x74 (116: 't')\n    0x00,0x00,0x08,0x08,0x08,0x0A,0x02,0x00, // Character 0x75 (117: 'u')\n    0x00,0x00,0x11,0x0A,0x0A,0x04,0x00,0x00, // Character 0x76 (118: 'v')\n    0x00,0x00,0x15,0x15,0x0B,0x0A,0x00,0x00, // Character 0x77 (119: 'w')\n    0x00,0x00,0x0A,0x06,0x04,0x0A,0x00,0x00, // Character 0x78 (120: 'x')\n    0x00,0x00,0x11,0x0A,0x0A,0x04,0x04,0x02, // Character 0x79 (121: 'y')\n    0x00,0x00,0x0E,0x0C,0x04,0x02,0x00,0x00, // Character 0x7a (122: 'z')\n    0x0C,0x04,0x04,0x04,0x04,0x04,0x04,0x0C, // Character 0x7b (123: '{')\n    0x04,0x04,0x04,0x04,0x04,0x04,0x00,0x00, // Character 0x7c (124: '|')\n    0x06,0x04,0x04,0x04,0x04,0x04,0x04,0x02, // Character 0x7d (125: '}')\n    0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00, // Character 0x7e (126: '~')\n};\n\n\nconst uint16_t lut[] = {\n    0, // Character 0x20 (32: ' ')\n    8, // Character 0x21 (33: '!')\n    16, // Character 0x22 (34: '\"')\n    24, // Character 0x23 (35: '#')\n    32, // Character 0x24 (36: '$')\n    40, // Character 0x25 (37: '%')\n    48, // Character 0x26 (38: '&')\n    56, // Character 0x27 (39: ''')\n    64, // Character 0x28 (40: '(')\n    72, // Character 0x29 (41: ')')\n    80, // Character 0x2a (42: '*')\n    88, // Character 0x2b (43: '+')\n    96, // Character 0x2c (44: ',')\n    104, // Character 0x2d (45: '-')\n    112, // Character 0x2e (46: '.')\n    120, // Character 0x2f (47: '/')\n    128, // Character 0x30 (48: '0')\n    136, // Character 0x31 (49: '1')\n    144, // Character 0x32 (50: '2')\n    152, // Character 0x33 (51: '3')\n    160, // Character 0x34 (52: '4')\n    168, // Character 0x35 (53: '5')\n    176, // Character 0x36 (54: '6')\n    184, // Character 0x37 (55: '7')\n    192, // Character 0x38 (56: '8')\n    200, // Character 0x39 (57: '9')\n    208, // Character 0x3a (58: ':')\n    216, // Character 0x3b (59: ';')\n    224, // Character 0x3c (60: '<')\n    232, // Character 0x3d (61: '=')\n    240, // Character 0x3e (62: '>')\n    248, // Character 0x3f (63: '?')\n    256, // Character 0x40 (64: '@')\n    264, // Character 0x41 (65: 'A')\n    272, // Character 0x42 (66: 'B')\n    280, // Character 0x43 (67: 'C')\n    288, // Character 0x44 (68: 'D')\n    296, // Character 0x45 (69: 'E')\n    304, // Character 0x46 (70: 'F')\n    312, // Character 0x47 (71: 'G')\n    320, // Character 0x48 (72: 'H')\n    328, // Character 0x49 (73: 'I')\n    336, // Character 0x4a (74: 'J')\n    344, // Character 0x4b (75: 'K')\n    352, // Character 0x4c (76: 'L')\n    360, // Character 0x4d (77: 'M')\n    368, // Character 0x4e (78: 'N')\n    376, // Character 0x4f (79: 'O')\n    384, // Character 0x50 (80: 'P')\n    392, // Character 0x51 (81: 'Q')\n    400, // Character 0x52 (82: 'R')\n    408, // Character 0x53 (83: 'S')\n    416, // Character 0x54 (84: 'T')\n    424, // Character 0x55 (85: 'U')\n    432, // Character 0x56 (86: 'V')\n    440, // Character 0x57 (87: 'W')\n    448, // Character 0x58 (88: 'X')\n    456, // Character 0x59 (89: 'Y')\n    464, // Character 0x5a (90: 'Z')\n    472, // Character 0x5b (91: '[')\n    480, // Character 0x5c (92: '\\')\n    488, // Character 0x5d (93: ']')\n    496, // Character 0x5e (94: '^')\n    504, // Character 0x5f (95: '_')\n    512, // Character 0x60 (96: '`')\n    520, // Character 0x61 (97: 'a')\n    528, // Character 0x62 (98: 'b')\n    536, // Character 0x63 (99: 'c')\n    544, // Character 0x64 (100: 'd')\n    552, // Character 0x65 (101: 'e')\n    560, // Character 0x66 (102: 'f')\n    568, // Character 0x67 (103: 'g')\n    576, // Character 0x68 (104: 'h')\n    584, // Character 0x69 (105: 'i')\n    592, // Character 0x6a (106: 'j')\n    600, // Character 0x6b (107: 'k')\n    608, // Character 0x6c (108: 'l')\n    616, // Character 0x6d (109: 'm')\n    624, // Character 0x6e (110: 'n')\n    632, // Character 0x6f (111: 'o')\n    640, // Character 0x70 (112: 'p')\n    648, // Character 0x71 (113: 'q')\n    656, // Character 0x72 (114: 'r')\n    664, // Character 0x73 (115: 's')\n    672, // Character 0x74 (116: 't')\n    680, // Character 0x75 (117: 'u')\n    688, // Character 0x76 (118: 'v')\n    696, // Character 0x77 (119: 'w')\n    704, // Character 0x78 (120: 'x')\n    712, // Character 0x79 (121: 'y')\n    720, // Character 0x7a (122: 'z')\n    728, // Character 0x7b (123: '{')\n    736, // Character 0x7c (124: '|')\n    744, // Character 0x7d (125: '}')\n    752, // Character 0x7e (126: '~')\n};\n\n\n"
  },
  {
    "path": "test/assets/monaco8.c-test",
    "content": "//\n// font\n// Font Size: 5x8px\n// Created: <timestamp>\n//\n\n#include <stdint.h>\n\n// \n// Pseudocode for retrieving data for a specific character:\n// \n// bytes_per_char = font_height * (font_width / 8 + ((font_width % 8) ? 1 : 0))\n// offset = (ascii_code(character) - ascii_code(' ')) * bytes_per_char\n// data = font[offset]\n// \n\nconst unsigned char font[] = {\n\t0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Character 0x20 (32: ' ')\n\t0x04,0x04,0x04,0x04,0x04,0x00,0x04,0x00, // Character 0x21 (33: '!')\n\t0x0A,0x0A,0x00,0x00,0x00,0x00,0x00,0x00, // Character 0x22 (34: '\"')\n\t0x00,0x08,0x0E,0x0A,0x0F,0x04,0x00,0x00, // Character 0x23 (35: '#')\n\t0x04,0x0E,0x06,0x04,0x0C,0x0C,0x0E,0x00, // Character 0x24 (36: '$')\n\t0x12,0x09,0x05,0x0C,0x16,0x14,0x01,0x00, // Character 0x25 (37: '%')\n\t0x06,0x05,0x06,0x02,0x0D,0x09,0x06,0x00, // Character 0x26 (38: '&')\n\t0x04,0x04,0x00,0x00,0x00,0x00,0x00,0x00, // Character 0x27 (39: ''')\n\t0x08,0x04,0x02,0x02,0x02,0x02,0x04,0x08, // Character 0x28 (40: '(')\n\t0x02,0x04,0x08,0x08,0x08,0x08,0x04,0x02, // Character 0x29 (41: ')')\n\t0x04,0x0E,0x0E,0x04,0x00,0x00,0x00,0x00, // Character 0x2a (42: '*')\n\t0x00,0x00,0x04,0x04,0x0F,0x04,0x00,0x00, // Character 0x2b (43: '+')\n\t0x00,0x00,0x00,0x00,0x00,0x04,0x04,0x04, // Character 0x2c (44: ',')\n\t0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00, // Character 0x2d (45: '-')\n\t0x00,0x00,0x00,0x00,0x00,0x04,0x04,0x00, // Character 0x2e (46: '.')\n\t0x00,0x08,0x00,0x04,0x02,0x02,0x01,0x00, // Character 0x2f (47: '/')\n\t0x06,0x0A,0x09,0x15,0x0B,0x0A,0x04,0x00, // Character 0x30 (48: '0')\n\t0x04,0x06,0x04,0x04,0x04,0x04,0x00,0x00, // Character 0x31 (49: '1')\n\t0x0E,0x08,0x08,0x04,0x02,0x02,0x00,0x00, // Character 0x32 (50: '2')\n\t0x06,0x08,0x0C,0x0C,0x08,0x08,0x06,0x00, // Character 0x33 (51: '3')\n\t0x00,0x0C,0x0A,0x08,0x0F,0x08,0x00,0x00, // Character 0x34 (52: '4')\n\t0x0E,0x02,0x02,0x0C,0x08,0x08,0x06,0x00, // Character 0x35 (53: '5')\n\t0x0C,0x02,0x00,0x0B,0x12,0x0A,0x04,0x00, // Character 0x36 (54: '6')\n\t0x0E,0x08,0x08,0x04,0x00,0x02,0x00,0x00, // Character 0x37 (55: '7')\n\t0x0C,0x0A,0x0E,0x0E,0x0A,0x0A,0x04,0x00, // Character 0x38 (56: '8')\n\t0x06,0x08,0x09,0x0E,0x08,0x08,0x06,0x00, // Character 0x39 (57: '9')\n\t0x00,0x00,0x04,0x00,0x00,0x04,0x04,0x00, // Character 0x3a (58: ':')\n\t0x00,0x00,0x04,0x00,0x00,0x04,0x04,0x04, // Character 0x3b (59: ';')\n\t0x00,0x00,0x00,0x04,0x02,0x0C,0x00,0x00, // Character 0x3c (60: '<')\n\t0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00, // Character 0x3d (61: '=')\n\t0x00,0x00,0x01,0x06,0x08,0x02,0x00,0x00, // Character 0x3e (62: '>')\n\t0x06,0x08,0x08,0x04,0x00,0x00,0x00,0x00, // Character 0x3f (63: '?')\n\t0x06,0x00,0x17,0x13,0x0F,0x00,0x04,0x00, // Character 0x40 (64: '@')\n\t0x04,0x04,0x0E,0x0A,0x0E,0x09,0x00,0x00, // Character 0x41 (65: 'A')\n\t0x06,0x0A,0x0A,0x06,0x0A,0x0A,0x00,0x00, // Character 0x42 (66: 'B')\n\t0x0C,0x02,0x00,0x01,0x02,0x02,0x0C,0x00, // Character 0x43 (67: 'C')\n\t0x06,0x08,0x00,0x00,0x08,0x0A,0x00,0x00, // Character 0x44 (68: 'D')\n\t0x0E,0x02,0x02,0x0E,0x02,0x02,0x00,0x00, // Character 0x45 (69: 'E')\n\t0x0E,0x02,0x02,0x0E,0x02,0x02,0x00,0x00, // Character 0x46 (70: 'F')\n\t0x0C,0x02,0x00,0x09,0x0A,0x0A,0x0C,0x00, // Character 0x47 (71: 'G')\n\t0x00,0x09,0x09,0x0F,0x09,0x09,0x00,0x00, // Character 0x48 (72: 'H')\n\t0x0E,0x04,0x04,0x04,0x04,0x04,0x00,0x00, // Character 0x49 (73: 'I')\n\t0x0C,0x08,0x08,0x08,0x08,0x08,0x06,0x00, // Character 0x4a (74: 'J')\n\t0x00,0x08,0x06,0x06,0x04,0x08,0x00,0x00, // Character 0x4b (75: 'K')\n\t0x00,0x02,0x02,0x02,0x02,0x02,0x00,0x00, // Character 0x4c (76: 'L')\n\t0x01,0x19,0x1B,0x1F,0x15,0x11,0x00,0x00, // Character 0x4d (77: 'M')\n\t0x00,0x03,0x03,0x05,0x0D,0x09,0x00,0x00, // Character 0x4e (78: 'N')\n\t0x06,0x0A,0x09,0x11,0x09,0x0A,0x04,0x00, // Character 0x4f (79: 'O')\n\t0x06,0x0A,0x0A,0x0E,0x02,0x02,0x00,0x00, // Character 0x50 (80: 'P')\n\t0x06,0x0A,0x09,0x11,0x09,0x0A,0x04,0x08, // Character 0x51 (81: 'Q')\n\t0x06,0x08,0x08,0x06,0x0C,0x08,0x00,0x00, // Character 0x52 (82: 'R')\n\t0x0C,0x02,0x02,0x0C,0x08,0x08,0x06,0x00, // Character 0x53 (83: 'S')\n\t0x0F,0x04,0x04,0x04,0x04,0x04,0x00,0x00, // Character 0x54 (84: 'T')\n\t0x00,0x01,0x01,0x01,0x09,0x0A,0x04,0x00, // Character 0x55 (85: 'U')\n\t0x01,0x01,0x0A,0x0A,0x06,0x04,0x00,0x00, // Character 0x56 (86: 'V')\n\t0x05,0x15,0x07,0x0F,0x0A,0x0A,0x00,0x00, // Character 0x57 (87: 'W')\n\t0x00,0x0A,0x06,0x04,0x0E,0x0A,0x00,0x00, // Character 0x58 (88: 'X')\n\t0x00,0x0A,0x0E,0x04,0x04,0x04,0x00,0x00, // Character 0x59 (89: 'Y')\n\t0x0E,0x08,0x04,0x04,0x02,0x02,0x00,0x00, // Character 0x5a (90: 'Z')\n\t0x0E,0x02,0x02,0x02,0x02,0x02,0x02,0x0E, // Character 0x5b (91: '[')\n\t0x01,0x02,0x02,0x04,0x00,0x08,0x00,0x00, // Character 0x5c (92: '\\')\n\t0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x07, // Character 0x5d (93: ']')\n\t0x00,0x04,0x0A,0x0A,0x00,0x00,0x00,0x00, // Character 0x5e (94: '^')\n\t0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E, // Character 0x5f (95: '_')\n\t0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Character 0x60 (96: '`')\n\t0x00,0x00,0x0E,0x0A,0x09,0x0A,0x00,0x00, // Character 0x61 (97: 'a')\n\t0x00,0x00,0x0E,0x0A,0x08,0x08,0x02,0x00, // Character 0x62 (98: 'b')\n\t0x00,0x00,0x0C,0x02,0x02,0x02,0x0C,0x00, // Character 0x63 (99: 'c')\n\t0x00,0x08,0x0E,0x0A,0x09,0x0A,0x00,0x00, // Character 0x64 (100: 'd')\n\t0x00,0x00,0x0E,0x0A,0x02,0x02,0x04,0x00, // Character 0x65 (101: 'e')\n\t0x0C,0x04,0x0E,0x00,0x00,0x00,0x00,0x00, // Character 0x66 (102: 'f')\n\t0x00,0x00,0x0E,0x0A,0x09,0x0A,0x08,0x0A, // Character 0x67 (103: 'g')\n\t0x00,0x00,0x0E,0x0A,0x08,0x08,0x00,0x00, // Character 0x68 (104: 'h')\n\t0x04,0x00,0x06,0x04,0x04,0x04,0x00,0x00, // Character 0x69 (105: 'i')\n\t0x08,0x00,0x0E,0x08,0x08,0x08,0x08,0x04, // Character 0x6a (106: 'j')\n\t0x02,0x02,0x0A,0x06,0x06,0x0A,0x00,0x00, // Character 0x6b (107: 'k')\n\t0x02,0x04,0x04,0x04,0x04,0x04,0x00,0x00, // Character 0x6c (108: 'l')\n\t0x00,0x00,0x0F,0x15,0x15,0x15,0x00,0x00, // Character 0x6d (109: 'm')\n\t0x00,0x00,0x0E,0x0A,0x08,0x08,0x00,0x00, // Character 0x6e (110: 'n')\n\t0x00,0x00,0x0E,0x08,0x01,0x0A,0x04,0x00, // Character 0x6f (111: 'o')\n\t0x00,0x00,0x0E,0x0A,0x08,0x08,0x02,0x00, // Character 0x70 (112: 'p')\n\t0x00,0x00,0x0E,0x0A,0x09,0x0A,0x08,0x08, // Character 0x71 (113: 'q')\n\t0x00,0x00,0x0E,0x02,0x02,0x02,0x00,0x00, // Character 0x72 (114: 'r')\n\t0x00,0x00,0x0E,0x02,0x0C,0x08,0x04,0x00, // Character 0x73 (115: 's')\n\t0x00,0x00,0x06,0x02,0x02,0x02,0x0C,0x00, // Character 0x74 (116: 't')\n\t0x00,0x00,0x08,0x08,0x08,0x0A,0x02,0x00, // Character 0x75 (117: 'u')\n\t0x00,0x00,0x11,0x0A,0x0A,0x04,0x00,0x00, // Character 0x76 (118: 'v')\n\t0x00,0x00,0x15,0x15,0x0B,0x0A,0x00,0x00, // Character 0x77 (119: 'w')\n\t0x00,0x00,0x0A,0x06,0x04,0x0A,0x00,0x00, // Character 0x78 (120: 'x')\n\t0x00,0x00,0x11,0x0A,0x0A,0x04,0x04,0x02, // Character 0x79 (121: 'y')\n\t0x00,0x00,0x0E,0x0C,0x04,0x02,0x00,0x00, // Character 0x7a (122: 'z')\n\t0x0C,0x04,0x04,0x04,0x04,0x04,0x04,0x0C, // Character 0x7b (123: '{')\n\t0x04,0x04,0x04,0x04,0x04,0x04,0x00,0x00, // Character 0x7c (124: '|')\n\t0x06,0x04,0x04,0x04,0x04,0x04,0x04,0x02, // Character 0x7d (125: '}')\n\t0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00, // Character 0x7e (126: '~')\n};\n\n\n"
  },
  {
    "path": "test/f2b_qt_compat_test.cpp",
    "content": "#include \"gtest/gtest.h\"\n#include \"f2b_qt_compat.h\"\n\n#include <optional>\n#include <vector>\n#include <set>\n#include <unordered_map>\n\n#include <QByteArray>\n#include <QBuffer>\n#include <QDataStream>\n#include <QString>\n\nusing namespace f2b;\n\ntemplate<typename T>\nvoid serialize_and_deserialize(const T& input, T& output)\n{\n    QByteArray b;\n    QBuffer buf(&b);\n    QDataStream s(&buf);\n\n    buf.open(QIODevice::WriteOnly);\n    s << input;\n    buf.close();\n\n    buf.open(QIODevice::ReadOnly);\n    s >> output;\n    buf.close();\n}\n\nTEST(SerializationTest, optional_int)\n{\n    std::optional<int> data {5};\n    std::optional<int> deserialized;\n\n    serialize_and_deserialize(data, deserialized);\n    EXPECT_TRUE(deserialized.has_value());\n    EXPECT_EQ(deserialized.value(), data.value());\n\n    data = {};\n    serialize_and_deserialize(data, deserialized);\n    EXPECT_FALSE(deserialized.has_value());\n}\n\nTEST(SerializationTest, optional_qstring)\n{\n    std::optional<QString> data { \"A quick brown fox jumps over a lazy dog.\" };\n    std::optional<QString> deserialized;\n\n    serialize_and_deserialize(data, deserialized);\n\n    EXPECT_TRUE(deserialized.has_value());\n    EXPECT_EQ(deserialized.value(), data.value());\n}\n\nTEST(SerializationTest, vector_bool)\n{\n    std::vector<bool> data;\n    std::vector<bool> deserialized = { true, true, false };\n\n    serialize_and_deserialize(data, deserialized);\n    EXPECT_TRUE(deserialized.empty());\n    EXPECT_EQ(data, deserialized);\n\n\n    data = { false, false, false, true, true, false, true, false, true, true, true };\n\n    serialize_and_deserialize(data, deserialized);\n    EXPECT_EQ(data, deserialized);\n}\n\nTEST(SerializationTest, set_size_t)\n{\n    std::set<std::size_t> data;\n    std::set<std::size_t> deserialized = { 1, 2, 3 };\n\n    serialize_and_deserialize(data, deserialized);\n    EXPECT_TRUE(deserialized.empty());\n    EXPECT_EQ(data, deserialized);\n\n\n    data = { 100, 152, 3, 6, 17, 0x541882, 1500, 4, 2, 8 };\n\n    serialize_and_deserialize(data, deserialized);\n    EXPECT_EQ(data, deserialized);\n}\n\nTEST(SerializationTest, unordered_map_int_qstring)\n{\n    std::unordered_map<int,QString> data;\n    std::unordered_map<int,QString> deserialized = { { 5, \"boo\" } };\n\n    serialize_and_deserialize(data, deserialized);\n    EXPECT_TRUE(deserialized.empty());\n    EXPECT_EQ(data, deserialized);\n\n\n    data = {\n        { 0, \"foo\" },\n        { 100, \"bar\" },\n        { -50, \"baz\" }\n    };\n\n    serialize_and_deserialize(data, deserialized);\n    EXPECT_EQ(data, deserialized);\n}\n\nTEST(SerializationTest, font_glyph)\n{\n    font::glyph data({2, 3}, { false, true, false, false, true, true });\n    font::glyph deserialized;\n\n    serialize_and_deserialize(data, deserialized);\n\n    EXPECT_EQ(data, deserialized);\n}\n\nTEST(SerializationTest, font_face)\n{\n    font::glyph glyph({2, 3}, { false, true, false, false, true, true });\n    font::face data({2, 3}, { glyph, glyph, glyph, glyph });\n    font::face deserialized;\n\n    serialize_and_deserialize(data, deserialized);\n\n    EXPECT_EQ(data, deserialized);\n}\n"
  },
  {
    "path": "test/sourcecodegeneration_test.cpp",
    "content": "#include \"gtest/gtest.h\"\n#include \"fontsourcecodegenerator.h\"\n#include \"fontfaceviewmodel.h\"\n#include <gsl/gsl>\n\n#include <chrono>\n#include <iostream>\n#include <vector>\n#include <numeric>\n\n#include <QString>\n#include <QFile>\n\nusing namespace f2b;\n\ntemplate<typename T>\nQString asset(const T& fileName)\n{\n    return QString(\"%1/%2\").arg(ASSETS_DIR, fileName);\n}\n\nclass test_source_code_generator : public font_source_code_generator\n{\npublic:\n    test_source_code_generator(source_code_options options): font_source_code_generator(options) {};\n\n    std::string current_timestamp() override {\n        return \"<timestamp>\";\n    }\n};\n\nTEST(SourceCodeGeneratorTest, GeneratorAll)\n{\n    auto faceFileName = asset(\"monaco8.fontedit\");\n    auto faceVM = std::make_unique<FontFaceViewModel>(faceFileName);\n\n    font::glyph_size expectedSize {5, 11};\n    font::margins expectedMargins {1, 2};\n\n    EXPECT_EQ(faceVM->face().num_glyphs(), 95);\n    EXPECT_EQ(faceVM->face().glyphs_size(), expectedSize);\n    EXPECT_EQ(faceVM->face().calculate_margins(), expectedMargins);\n\n    source_code_options options;\n    options.export_method = f2b::source_code_options::export_all;\n\n    test_source_code_generator g(options);\n\n\n    auto sourceCode = g.generate<format::c>(faceVM->face());\n\n    auto sourceCodeFileName = asset(\"monaco8.c-test\");\n    QFile f(sourceCodeFileName);\n    f.open(QFileDevice::ReadOnly);\n    auto referenceSourceCode = f.readAll().toStdString();\n    f.close();\n\n    ASSERT_EQ(sourceCode, referenceSourceCode);\n}\n\nTEST(SourceCodeGeneratorTest, GeneratorSubset)\n{\n    auto faceFileName = asset(\"monaco8.fontedit\");\n    auto faceVM = std::make_unique<FontFaceViewModel>(faceFileName);\n\n    font::glyph_size expectedSize {5, 11};\n    font::margins expectedMargins {1, 2};\n\n    EXPECT_EQ(faceVM->face().num_glyphs(), 95);\n    EXPECT_EQ(faceVM->face().glyphs_size(), expectedSize);\n    EXPECT_EQ(faceVM->face().calculate_margins(), expectedMargins);\n\n    source_code_options options;\n    options.export_method = f2b::source_code_options::export_selected;\n    options.indentation = f2b::source_code::space { 4 };\n\n    test_source_code_generator g(options);\n\n\n    auto sourceCode = g.generate<format::c>(faceVM->face());\n\n    auto sourceCodeFileName = asset(\"monaco8-subset.c-test\");\n    QFile f(sourceCodeFileName);\n    f.open(QFileDevice::ReadOnly);\n    auto referenceSourceCode = f.readAll().toStdString();\n    f.close();\n\n    ASSERT_EQ(sourceCode, referenceSourceCode);\n}\n\nTEST(SourceCodeGeneratorTest, GeneratorPerformance)\n{\n    auto faceFileName = asset(\"jetbrains260.fontedit\");\n    auto faceVM = std::make_unique<FontFaceViewModel>(faceFileName);\n\n    f2b::source_code_options::export_method_type export_methods[] = {\n        f2b::source_code_options::export_selected,\n        f2b::source_code_options::export_all\n    };\n\n    for (auto export_method : gsl::span(export_methods)) {\n        source_code_options options;\n        options.export_method = export_method;\n        font_source_code_generator g(std::move(options));\n\n        std::vector<std::chrono::high_resolution_clock::rep> durations;\n        for (int i = 0; i < 5; ++i) {\n            auto start = std::chrono::high_resolution_clock::now();\n            g.generate<format::c>(faceVM->face());\n            auto end = std::chrono::high_resolution_clock::now();\n            durations.push_back(std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count());\n        }\n        std::cout << \"Export Method \" << export_method << \": \";\n        for (auto& d : durations) {\n            std::cout << d << \" \";\n        }\n        std::cout << \"(mean: \" << std::accumulate(durations.begin(), durations.end(), 0) / durations.size() << \")\";\n        std::cout << std::endl;\n    }\n\n}\n"
  }
]