├── .lgtm.yml ├── CHANGES.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DOCUMENTATION.md ├── INSTALL.md ├── LICENSE ├── Makefile.in ├── NOTICE ├── README.md ├── SECURITY.md ├── TESTSUITE.md ├── TODO.md ├── common-private.h ├── common.c ├── common.h ├── config.guess ├── config.sub ├── configure ├── configure.ac ├── css-compute.c ├── css-core.c ├── css-import.c ├── css-private.h ├── css-rule.c ├── css.h ├── default-css.h ├── default.css ├── dict.c ├── dict.h ├── file-private.h ├── file.c ├── file.h ├── font-core.c ├── font-extents.c ├── font-find.c ├── font-private.h ├── font.h ├── html-attr.c ├── html-core.c ├── html-find.c ├── html-load.c ├── html-node.c ├── html-private.h ├── html.h ├── htmlcss-512.png ├── htmlcss.h ├── htmlcss.html ├── htmlcss.pc.in ├── htmlcss.svg ├── htmlcss.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── image.c ├── image.h ├── install-sh ├── pool-private.h ├── pool.c ├── pool.h ├── run.h ├── sha3.c ├── sha3.h ├── testhtmlcss.c └── testhtmlcss.html /.lgtm.yml: -------------------------------------------------------------------------------- 1 | queries: 2 | - exclude: cpp/uncontrolled-allocation-size 3 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | Changes in HTMLCSS 2 | ================== 3 | 4 | 5 | v0.1 - YYYY-MM-DD 6 | ----------------- 7 | 8 | - Initial release. 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Code of Conduct 2 | =============== 3 | 4 | My goal is to provide quality open source software that everyone can use. 5 | While I may not be able to address every request or accept every contribution 6 | to this project, I will do my best to develop and maintain it for the common 7 | good. As part of the open source community, I expect everyone to: 8 | 9 | - Be friendly and patient. 10 | - Be respectful, even if we disagree. 11 | - Be honest. 12 | - Be accepting of all people. 13 | - Fully explain your concerns, issues, or ideas. 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing to HTMLCSS 2 | ===================== 3 | 4 | HTMLCSS is developed and distributed as open source software under the Apache 5 | License, Version 2.0. Contributions should be submitted as pull requests on 6 | the Github site: 7 | 8 | http://github.com/michaelrsweet/htmlcss/pulls 9 | 10 | 11 | Contents 12 | -------- 13 | 14 | - [Build System](#build-system) 15 | - [Version Numbering](#version-numbering) 16 | - [Coding Guidelines](#coding-guidelines) 17 | - [Source Files](#source-files) 18 | - [Header Files](#header-files) 19 | - [Comments](#comments) 20 | - [Indentation](#indentation) 21 | - [Spacing](#spacing) 22 | - [Return Values](#return-values) 23 | - [Functions](#functions) 24 | - [Variables](#variables) 25 | - [Types](#types) 26 | - [Structures](#structures) 27 | - [Constants](#constants) 28 | - [Shell Script Guidelines](#shell-script-guidelines) 29 | - [Makefile Guidelines](#makefile-guidelines) 30 | - [General Organization](#general-organization) 31 | - [Makefile Documentation](#makefile-documentation) 32 | - [Portable Makefile Construction](#portable-makefile-construction) 33 | - [Standard Variables](#standard-variables) 34 | - [Standard Targets](#standard-targets) 35 | - [Object Files](#object-files) 36 | - [Programs](#programs) 37 | - [Static Libraries](#static-libraries) 38 | - [Shared Libraries](#shared-libraries) 39 | - [Dependencies](#dependencies) 40 | - [Install/Uninstall Support](#installuninstall-support) 41 | 42 | 43 | Build System 44 | ------------ 45 | 46 | The build system uses [GNU autoconf][AUTOCONF] to create a simple POSIX makefile 47 | to build static and/or shared libraries. To improve portability, makefiles 48 | *must not* make use of features unique to GNU make. See the 49 | [Makefile Guidelines](#makefile-guidelines) section for a description of the 50 | allowed make features and makefile guidelines. 51 | 52 | An Xcode project is provided for macOS/iOS developers, and a Visual Studio 53 | solution and projects for Windows developers. 54 | 55 | [AUTOCONF]: https://www.gnu.org/software/autoconf/ 56 | 57 | 58 | Version Numbering 59 | ----------------- 60 | 61 | HTMLCSS uses a three-part version number separated by periods to represent the 62 | major, minor, and patch release numbers. Major release numbers indicate large 63 | design changes or backwards-incompatible changes to the library. Minor release 64 | numbers indicate new features and other smaller changes which are backwards- 65 | compatible with previous releases. Patch numbers indicate bug fixes to the 66 | previous feature or patch release. 67 | 68 | Production releases use the plain version numbers: 69 | 70 | MAJOR.MINOR.PATCH 71 | 1.0.0 72 | 1.0.1 73 | 1.0.2 74 | ... 75 | 1.1.0 76 | ... 77 | 2.0.0 78 | 79 | The first production release in a MAJOR.MINOR series (MAJOR.MINOR.0) is called 80 | a feature release. Feature releases are the only releases that may contain new 81 | features. Subsequent production releases in a MAJOR.MINOR series may only 82 | contain bug fixes. 83 | 84 | Beta-test releases are identified by appending the letter B to the major and 85 | minor version numbers followed by the beta release number: 86 | 87 | MAJOR.MINORbNUMBER 88 | 1.0b1 89 | 90 | Release candidates are identified by appending the letters RC to the major and 91 | minor version numbers followed by the release candidate number: 92 | 93 | MAJOR.MINORrcNUMBER 94 | 1.0rc1 95 | 96 | > Note: While the beta/release candidate syntax is *not* strictly compatible 97 | > with [Semantic Versioning](https://semver.org), it is better supported by the 98 | > various traditional package formats. When packaging a pre-release version of 99 | > HTMLCSS in a format that requires the use of semantic version numbers, the 100 | > version number should simply be converted to the form "MAJOR.MINOR.0-suffix". 101 | 102 | 103 | Coding Guidelines 104 | ----------------- 105 | 106 | Contributed source code must follow the guidelines below. While the examples 107 | are for C source files, source code for other languages should conform to the 108 | same guidelines as allowed by the language. 109 | 110 | 111 | ### Source Files 112 | 113 | All source files names must be 16 characters or less in length to ensure 114 | compatibility with older UNIX filesystems. Source files containing functions 115 | have an extension of ".c" for C source files. All "include" files have an 116 | extension of ".h". Tabs are set to 8 characters or columns. 117 | 118 | The top of each source file contains a header giving the purpose or nature of 119 | the source file and the copyright and licensing notice: 120 | 121 | // 122 | // Description of file contents. 123 | // 124 | // Copyright © YYYY by AUTHOR. 125 | // 126 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 127 | // information. 128 | // 129 | 130 | 131 | ### Header Files 132 | 133 | Private API header files must be named with the suffix "-private", for example 134 | the "hc.h" header file defines all of the public APIs while the 135 | "hc-private.h" header file defines all of the private APIs. Typically a 136 | private API header file will include the corresponding public API header file. 137 | 138 | 139 | ### Comments 140 | 141 | All source code utilizes block comments within functions to describe the 142 | operations being performed by a group of statements; avoid putting a comment 143 | per line unless absolutely necessary, and then consider refactoring the code 144 | so that it is not necessary. C source files use the C99 comment format 145 | ("// comment"): 146 | 147 | // Clear the state array before we begin... 148 | for (i = 0; i < (sizeof(array) / sizeof(sizeof(array[0])); i ++) 149 | array[i] = HC_STATE_IDLE; 150 | 151 | // Wait for state changes on another thread... 152 | do 153 | { 154 | for (i = 0; i < (sizeof(array) / sizeof(sizeof(array[0])); i ++) 155 | if (array[i] != HC_STATE_IDLE) 156 | break; 157 | 158 | if (i == (sizeof(array) / sizeof(array[0]))) 159 | sleep(1); 160 | } while (i == (sizeof(array) / sizeof(array[0]))); 161 | 162 | 163 | ### Indentation 164 | 165 | All code blocks enclosed by brackets begin with the opening brace on a new 166 | line. The code then follows starting on a new line after the brace and is 167 | indented 2 spaces. The closing brace is then placed on a new line following 168 | the code at the original indentation: 169 | 170 | { 171 | int i; // Looping var 172 | 173 | // Process foobar values from 0 to 999... 174 | for (i = 0; i < 1000; i ++) 175 | { 176 | do_this(i); 177 | do_that(i); 178 | } 179 | } 180 | 181 | Single-line statements following "do", "else", "for", "if", and "while" are 182 | indented 2 spaces as well. Blocks of code in a "switch" block are indented 4 183 | spaces after each "case" and "default" case: 184 | 185 | switch (array[i]) 186 | { 187 | case HC_STATE_IDLE : 188 | do_this(i); 189 | do_that(i); 190 | break; 191 | 192 | default : 193 | do_nothing(i); 194 | break; 195 | } 196 | 197 | 198 | ### Spacing 199 | 200 | A space follows each reserved word such as `if`, `while`, etc. Spaces are not 201 | inserted between a function name and the arguments in parenthesis. 202 | 203 | 204 | ### Return Values 205 | 206 | Parenthesis surround values returned from a function: 207 | 208 | return (HC_STATE_IDLE); 209 | 210 | 211 | ### Functions 212 | 213 | Functions with a global scope have a lowercase prefix followed by capitalized 214 | words, e.g., `hcDoThis`, `hcDoThat`, `hcDoSomethingElse`, etc. Private 215 | global functions begin with a leading underscore, e.g., `_hcDoThis`, 216 | `_hcDoThat`, etc. 217 | 218 | Functions with a local scope are declared static with lowercase names and 219 | underscores between words, e.g., `do_this`, `do_that`, `do_something_else`, etc. 220 | 221 | Function names follow the following pattern: 222 | 223 | - "hcFooCreate" to create a Foo object, 224 | - "hcFooDelete" to destroy (free) a Foo object, 225 | - "hcFooGetBar" to get data element Bar from object Foo, 226 | - "hcFooIsBar" to test condition Bar for object Foo, and 227 | - "hcFooSetBar" to set data element Bar in object Foo. 228 | - "hcFooVerb" to take an action with object Foo. 229 | 230 | Each function begins with a comment header describing what the function does, 231 | the possible input limits (if any), the possible output values (if any), and 232 | any special information needed: 233 | 234 | // 235 | // 'hcDoThis()' - Short description of function. 236 | // 237 | // Longer documentation for function with examples using a subset of 238 | // markdown. This is a bulleted list: 239 | // 240 | // - One fish 241 | // - Two fish 242 | // - Red fish 243 | // - Blue fish 244 | // 245 | // > *Note:* Special notes for developer should be markdown block quotes. 246 | // 247 | 248 | float // O - Inverse power value, 0.0 <= y <= 1.1 249 | hcDoThis(float x) // I - Power value (0.0 <= x <= 1.1) 250 | { 251 | ... 252 | return (y); 253 | } 254 | 255 | Return/output values are indicated using an "O" prefix, input values are 256 | indicated using the "I" prefix, and values that are both input and output use 257 | the "IO" prefix for the corresponding in-line comment. 258 | 259 | The [`codedoc` documentation generator][1] also understands the following 260 | special text in the function description comment: 261 | 262 | @deprecated@ - Marks the function as deprecated (not recommended 263 | for new development and scheduled for removal) 264 | @since version@ - Marks the function as new in the specified version. 265 | @private@ - Marks the function as private (same as starting the 266 | function name with an underscore) 267 | 268 | [1]: https://www.msweet.org/codedoc 269 | 270 | 271 | ### Variables 272 | 273 | Variables with a global scope are capitalized, e.g., `ThisVariable`, 274 | `ThatVariable`, `ThisStateVariable`, etc. Globals *must not* be used in the 275 | HTMLCSS library. 276 | 277 | Variables with a local scope are lowercase with underscores between words, 278 | e.g., `this_variable`, `that_variable`, etc. Any "local global" variables 279 | shared by functions within a source file are declared static. 280 | 281 | Each variable is declared on a separate line and is immediately followed by a 282 | comment block describing the variable: 283 | 284 | int ThisVariable; // The current state of this 285 | static int that_variable; // The current state of that 286 | 287 | 288 | ### Types 289 | 290 | All type names are lowercase with underscores between words and `_t` appended 291 | to the end of the name, e.g., `hc_this_type_t`, `hc_that_type_t`, etc. 292 | Type names start with the "hc\_" prefix to avoid conflicts with system types. 293 | Private type names start with an underscore, e.g., `_hc_this_t`, 294 | `_hc_that_t`, etc. 295 | 296 | Each type has a comment block immediately after the typedef: 297 | 298 | typedef int hc_this_type_t; // This type is for foobar options. 299 | 300 | 301 | ### Structures 302 | 303 | All structure names are lowercase with underscores between words and `_s` 304 | appended to the end of the name, e.g., `hc_this_s`, `hc_that_s`, etc. 305 | Structure names start with the "hc\_" prefix to avoid conflicts with system 306 | types. Private structure names start with an underscore, e.g., `_hc_this_s`, 307 | `_hc_that_s`, etc. 308 | 309 | Each structure has a comment block immediately after the struct and each member 310 | is documented similar to the variable naming policy above: 311 | 312 | struct hc_this_struct_s // This structure is for foobar options. 313 | { 314 | int this_member; // Current state for this 315 | int that_member; // Current state for that 316 | }; 317 | 318 | One common design pattern is to define a private structure with a public 319 | typedef, for example: 320 | 321 | // In public header 322 | typedef struct _hc_foo_s hc_foo_t // Foo object 323 | 324 | // In private header 325 | struct _hc_foo_s // Foo object 326 | { 327 | int this_member; // Current state for this 328 | int that_member; // Current state for that 329 | }; 330 | 331 | 332 | ### Constants 333 | 334 | All constant names are uppercase with underscores between words, e.g., 335 | `HC_THIS_CONSTANT`, `HC_THAT_CONSTANT`, etc. Constants begin with the 336 | "HC\_" prefix to avoid conflicts with system constants. Private constants 337 | start with an underscore, e.g., `_HC_THIS_CONSTANT`, 338 | `_HC_THAT_CONSTANT`, etc. 339 | 340 | Typed enumerations should be used whenever possible to allow for type checking 341 | by the compiler. The constants for typed enumerations must match the type name 342 | in uppercase, for example a `hc_foo_e` enumeration has constant names 343 | starting with `HC_FOO_`. 344 | 345 | Comment blocks immediately follow each constant: 346 | 347 | typedef enum hc_style_e // Style enumerations 348 | { 349 | HC_STYLE_THIS, // This style 350 | HC_STYLE_THAT // That style 351 | } hc_style_t; 352 | 353 | 354 | Shell Script Guidelines 355 | ----------------------- 356 | 357 | All shell scripts in HTMLCSS must conform to the [POSIX shell][POSIX-SHELL] 358 | command language and should restrict their dependence on non-POSIX utility 359 | commands. 360 | 361 | [POSIX-SHELL]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18 362 | 363 | 364 | Makefile Guidelines 365 | ------------------- 366 | 367 | HTMLCSS uses a single [POSIX makefile][POSIX-MAKE] to build it. GNU make 368 | extensions MUST NOT be used. 369 | 370 | [POSIX-MAKE]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html 371 | 372 | The following variables are defined in the makefile: 373 | 374 | - `AR`; the static library archiver command, 375 | - `ARFLAGS`; options for the static library archiver, 376 | - `CC`; the C compiler command, 377 | - `CFLAGS`; options for the C compiler, 378 | - `CODESIGN_IDENTITY`: the code signing identity, 379 | - `CPPFLAGS`; options for the C preprocessor, 380 | - `DESTDIR`/`DSTROOT`: the destination root directory when installing. 381 | - `DSO`; the shared library building command, 382 | - `DSOFLAGS`; options for the shared library building command, 383 | - `LDFLAGS`; options for the linker, 384 | - `LIBHC`: the name of the primary (shared or static) library 385 | - `LIBHC_STATIC`: the name of the secondary (static) library 386 | - `LIBS`; libraries for all programs, 387 | - `OPTIM`; common compiler optimization options, 388 | - `prefix`; the installation prefix directory, 389 | - `RANLIB`; the static library indexing command, 390 | - `SHELL`; the sh (POSIX shell) command, 391 | - `VERSION`: the library version number. 392 | 393 | The following standard targets are defined in the makefile: 394 | 395 | - `all`; creates the static library and unit test program. 396 | - `clean`; removes all target programs libraries, documentation files, and 397 | object files, 398 | - `install`; installs all distribution files in their corresponding locations. 399 | - `test`; runs the unit test program, building it as needed. 400 | -------------------------------------------------------------------------------- /DOCUMENTATION.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: How to Use HTMLCSS 3 | author: Michael R Sweet 4 | copyright: Copyright © 2018-2025 by Michael R Sweet 5 | version: 0.1 6 | ... 7 | 8 | Contents 9 | ======== 10 | 11 | [How to Use HTMLCSS](@) 12 | 13 | [Example](@) 14 | 15 | [Reference](@) 16 | 17 | 18 | How to Use HTMLCSS 19 | ================== 20 | 21 | 22 | Example 23 | ======= 24 | 25 | 26 | Reference 27 | ========= 28 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | Build Instructions 2 | ================== 3 | 4 | HTMLCSS requires a C99 compiler like GCC, Clang, or Visual C, along with 5 | POSIX-compliant `make` and `sh` programs (Linux/Unix), Visual Studio (Windows), 6 | and/or Xcode (macOS) for doing the build. The only required library is ZLIB 7 | (1.1 or later) for compression support. 8 | 9 | 10 | Getting Prerequisites 11 | --------------------- 12 | 13 | Run the following commands on CentOS/Fedora/RHEL: 14 | 15 | sudo dnf groupinstall 'Development Tools' 16 | sudo dnf install zlib-devel 17 | 18 | Run the following commands on Debian/Raspbian/Ubuntu: 19 | 20 | sudo apt-get install build-essential zlib1g-dev 21 | 22 | Install Xcode from the AppStore on macOS. 23 | 24 | Install Visual Studio 2019 or later on Windows. The Visual Studio solution 25 | "htmlcss.sln" will download all the prerequisite NuGet packages. 26 | 27 | 28 | Building HTMLCSS 29 | ---------------- 30 | 31 | HTMLCSS uses the usual `configure` script to generate a `make` file: 32 | 33 | ./configure [options] 34 | make 35 | 36 | Use `./configure --help` to see a full list of options. 37 | 38 | There is also an Xcode project under the `xcode` directory that can be used on 39 | macOS: 40 | 41 | open xcode/htmlcss.xcodeproj 42 | 43 | and a Visual Studio solution under the `vcnet` directory that must be used on 44 | Windows. 45 | 46 | You can test the build by running the HTMLCSS test program: 47 | 48 | make test 49 | 50 | 51 | Installing HTMLCSS 52 | ---------------- 53 | 54 | Once you have successfully built HTMLCSS, install it using: 55 | 56 | sudo make install 57 | 58 | By default everything will be installed under `/usr/local`. Use the `--prefix` 59 | configure option to override the base installation directory. Set the 60 | `DESTDIR`, `DSTROOT`, or `RPM_BUILD_ROOT` environment variables to redirect the 61 | installation to a staging area, as is typically done for most software packaging 62 | systems (using one of those environment variables...) 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile.in: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile for HTMLCSS library. 3 | # 4 | # https://github.com/michaelrsweet/htmlcss 5 | # 6 | # Copyright © 2018-2025 by Michael R Sweet. 7 | # 8 | # Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | # information. 10 | # 11 | 12 | # POSIX makefile 13 | .POSIX: 14 | 15 | 16 | # Build silently 17 | .SILENT: 18 | 19 | 20 | # Version numbers... 21 | HTMLCSS_VERSION = @HTMLCSS_VERSION@ 22 | HTMLCSS_VERSION_MAJOR = @HTMLCSS_VERSION_MAJOR@ 23 | HTMLCSS_VERSION_MINOR = @HTMLCSS_VERSION_MINOR@ 24 | 25 | 26 | # Programs and options... 27 | AR = @AR@ 28 | ARFLAGS = @ARFLAGS@ 29 | CC = @CC@ 30 | CFLAGS = @CFLAGS@ $(CPPFLAGS) $(OPTIM) $(WARNINGS) 31 | CODE_SIGN = @CODE_SIGN@ 32 | CODESIGN_IDENTITY = - 33 | CPPFLAGS = @CPPFLAGS@ '-DVERSION="$(HTMLCSS_VERSION)"' 34 | CSFLAGS = -s "$(CODESIGN_IDENTITY)" @CSFLAGS@ --timestamp 35 | DSOFLAGS = @DSOFLAGS@ $(CFLAGS) 36 | INSTALL = @INSTALL@ 37 | LDFLAGS = @LDFLAGS@ $(OPTIM) 38 | LIBS = @LIBS@ -lm 39 | LN = @LN@ 40 | OPTIM = @OPTIM@ 41 | RANLIB = @RANLIB@ 42 | RM = @RM@ -f 43 | RMDIR = @RMDIR@ 44 | SHELL = /bin/sh 45 | WARNINGS = @WARNINGS@ 46 | 47 | 48 | # Targets 49 | LIBHTMLCSS = @LIBHTMLCSS@ 50 | LIBHTMLCSS_STATIC = @LIBHTMLCSS_STATIC@ 51 | 52 | 53 | # Directories... 54 | bindir = @bindir@ 55 | datadir = @datadir@ 56 | datarootdir = @datarootdir@ 57 | exec_prefix = @exec_prefix@ 58 | includedir = @includedir@ 59 | infodir = @infodir@ 60 | libdir = @libdir@ 61 | libexecdir = @libexecdir@ 62 | localstatedir = @localstatedir@ 63 | mandir = @mandir@ 64 | oldincludedir = @oldincludedir@ 65 | prefix = @prefix@ 66 | sbindir = @sbindir@ 67 | sharedstatedir = @sharedstatedir@ 68 | srcdir = @srcdir@ 69 | sysconfdir = @sysconfdir@ 70 | top_srcdir = @top_srcdir@ 71 | 72 | BUILDROOT = $(DSTROOT)$(RPM_BUILD_ROOT)$(DESTDIR) 73 | 74 | 75 | # Build commands... 76 | .SUFFIXES: .c .h .o 77 | .c.o: 78 | echo Compiling $<... 79 | $(CC) $(CFLAGS) -c -o $@ $< 80 | 81 | 82 | # Files... 83 | PUBHEADERS = \ 84 | common.h \ 85 | css.h \ 86 | dict.h \ 87 | file.h \ 88 | font.h \ 89 | html.h \ 90 | htmlcss.h \ 91 | image.h \ 92 | pool.h \ 93 | run.h \ 94 | sha3.h 95 | 96 | PRIVHEADERS = \ 97 | common-private.h \ 98 | css-private.h \ 99 | file-private.h \ 100 | font-private.h \ 101 | html-private.h \ 102 | pool-private.h 103 | 104 | LIBOBJS = \ 105 | common.o \ 106 | css-compute.o \ 107 | css-core.o \ 108 | css-import.o \ 109 | css-rule.o \ 110 | dict.o \ 111 | file.o \ 112 | font-core.o \ 113 | font-extents.o \ 114 | font-find.o \ 115 | html-attr.o \ 116 | html-core.o \ 117 | html-find.o \ 118 | html-load.o \ 119 | html-node.o \ 120 | image.o \ 121 | pool.o \ 122 | sha3.o 123 | 124 | OBJS = testhtmlcss.o $(LIBOBJS) 125 | 126 | TARGETS = \ 127 | $(LIBHTMLCSS) \ 128 | $(LIBHTMLCSS_STATIC) \ 129 | testhtmlcss 130 | 131 | DOCFILES = \ 132 | htmlcss.html \ 133 | htmlcss.epub \ 134 | htmlcss-512.png 135 | DOCFLAGS = \ 136 | --author "Michael R Sweet" \ 137 | --copyright "Copyright (c) 2018-2025 by Michael R Sweet" \ 138 | --docversion $(VERSION) \ 139 | --title "HTMLCSS Programming Manual" 140 | DOCSOURCES = \ 141 | $(PUBHEADERS) \ 142 | $(LIBOBJS:.o=.c) 143 | 144 | 145 | # Make everything 146 | all: $(TARGETS) 147 | 148 | 149 | # Clean everything 150 | clean: 151 | rm -f $(TARGETS) $(OBJS) 152 | 153 | 154 | # Install everything 155 | install: $(TARGETS) 156 | echo Installing header files to $(BUILDROOT)$(includedir)... 157 | $(INSTALL) -d -m 755 $(BUILDROOT)$(includedir) 158 | for file in $(PUBHEADERS); do \ 159 | $(INSTALL) -c -m 644 $$file $(BUILDROOT)$(includedir); \ 160 | done 161 | echo Installing library files to $(BUILDROOT)$(libdir)... 162 | $(INSTALL) -d -m 755 $(BUILDROOT)$(libdir) 163 | if test "x$(LIBHTMLCSS_STATIC)" != x; then \ 164 | $(INSTALL) -c -m 644 $(LIBHTMLCSS_STATIC) $(BUILDROOT)$(libdir); \ 165 | $(RANLIB) $(BUILDROOT)$(libdir)/$(LIBHTMLCSS_STATIC); \ 166 | fi 167 | if test "x$(LIBHTMLCSS)" = xlibhtmlcss.so.1; then \ 168 | $(INSTALL) -c -m 755 libhtmlcss.so.1 $(BUILDROOT)$(libdir); \ 169 | ln -sf libhtmlcss.so.1 $(BUILDROOT)$(libdir)/libhtmlcss.so; \ 170 | elif test "x$(LIBHTMLCSS)" = xlibhtmlcss.1.dylib; then \ 171 | $(INSTALL) -c -m 755 libhtmlcss.1.dylib $(BUILDROOT)$(libdir); \ 172 | codesign -s "$(CODESIGN_IDENTITY)" -o runtime --timestamp $(BUILDROOT)$(libdir)/libhtmlcss.1.dylib; \ 173 | ln -sf libhtmlcss.1.dylib $(BUILDROOT)$(libdir)/libhtmlcss.dylib; \ 174 | else \ 175 | $(INSTALL) -c -m 644 $(LIBHTMLCSS) $(BUILDROOT)$(libdir); \ 176 | $(RANLIB) $(BUILDROOT)$(libdir)/$(LIBHTMLCSS); \ 177 | fi 178 | echo Installing pkg-config files to $(BUILDROOT)$(libdir)/pkgconfig... 179 | $(INSTALL) -d -m 755 $(BUILDROOT)$(libdir)/pkgconfig 180 | $(INSTALL) -c -m 644 htmlcss.pc $(BUILDROOT)$(libdir)/pkgconfig 181 | echo Installing documentation to $(BUILDROOT)$(datadir)/doc/htmlcss... 182 | $(INSTALL) -d -m 755 $(BUILDROOT)$(datadir)/doc/htmlcss 183 | for file in $(DOCFILES); do \ 184 | $(INSTALL) -c -m 644 $$file $(BUILDROOT)$(datadir)/doc/htmlcss; \ 185 | done 186 | echo Installing man page to $(BUILDROOT)$(mandir)/man3... 187 | $(INSTALL) -d -m 755 $(BUILDROOT)$(mandir)/man3 188 | $(INSTALL) -c -m 644 doc/htmlcss.3 $(BUILDROOT)$(mandir)/man3 189 | 190 | 191 | # Make documentation 192 | doc: 193 | codedoc $(DOCFLAGS) $(DOCSOURCES) >htmlcss.html 194 | 195 | 196 | # Run unit tests 197 | test: testhtmlcss 198 | ./testhtmlcss --all --css --font --html testhtmlcss.html >testhtmlcss.log 2>&1 || (cat testhtmlcss.log; exit 1) 199 | 200 | 201 | test-fonts: testhtmlcss 202 | ./testhtmlcss --font testsuite/Source/*.ttf testsuite/Source/*.otf 203 | 204 | 205 | # Unit test program 206 | testhtmlcss: testhtmlcss.o libhtmlcss.a 207 | $(CC) $(LDFLAGS) -o testhtmlcss testhtmlcss.o libhtmlcss.a $(LIBS) 208 | 209 | 210 | # Library 211 | libhtmlcss.a: $(LIBOBJS) 212 | echo Archiving $@... 213 | $(RM) $@ 214 | $(AR) $(ARFLAGS) $@ $(LIBOBJS) 215 | $(RANLIB) $@ 216 | 217 | libhtmlcss.so.1: $(LIBOBJS) 218 | echo Linking $@... 219 | $(CC) $(DSOFLAGS) -shared -o $@ -Wl,-soname,$@ $(LIBOBJS) $(LIBS) 220 | 221 | libhtmlcss.1.dylib: $(LIBOBJS) 222 | echo Linking $@... 223 | $(CC) $(DSOFLAGS) -dynamiclib -o $@ -install_name $(libdir)/$@ -current_version $(PDFIO_VERSION_MAJOR).$(PDFIO_VERSION_MINOR) -compatibility_version 1.0 $(LIBOBJS) $(LIBS) 224 | 225 | 226 | # htmlcss1.def (Windows DLL exports file...) 227 | # 228 | # I'd love to use __declspec(dllexport) but MS puts it before the function 229 | # declaration instead of after like everyone else, and it breaks Codedoc and 230 | # other tools I rely on... 231 | htmlcss1.def: $(LIBOBJS) Makefile 232 | echo Generating $@... 233 | echo "LIBRARY htmlcss1" >$@ 234 | echo "VERSION $(HTMLCSS_VERSION_MAJOR).$(HTMLCSS_VERSION_MINOR)" >>$@ 235 | echo "EXPORTS" >>$@ 236 | nm $(LIBOBJS) 2>/dev/null | grep "T _" | awk '{print $$3}' | \ 237 | grep -v '^_ttf' | sed -e '1,$$s/^_//' | sort >>$@ 238 | 239 | 240 | # Dependencies 241 | css-import.o: default-css.h 242 | 243 | default-css.h: default.css Makefile 244 | echo "static const char *default_css =" >default-css.h 245 | sed -e '1,$$s/\\/\\\\/g' -e '1,$$s/"/\\"/g' >default-css.h 246 | echo ";" >>default-css.h 247 | 248 | $(OBJS): Makefile $(PUBHEADERS) $(PRIVHEADERS) 249 | 250 | 251 | # Scan code with the Clang static analyzer 252 | clang: 253 | clang $(CPPFLAGS) -DDEBUG -Werror --analyze $(LIBOBJS:.o=.c) 254 | rm -rf $(LIBOBJS:.o=.plist) 255 | 256 | 257 | # Scan the code using Cppcheck 258 | cppcheck: 259 | cppcheck --template=gcc --suppress=cert-API01-C --suppress=cert-EXP05-C --suppress=cert-INT31-c --suppress=cert-MSC24-C --suppress=cert-STR05-C $(CPPFLAGS) $(LIBOBJS:.o=.c) 2>cppcheck.log 260 | @test -s cppcheck.log && (echo ""; echo "Errors detected:"; echo ""; cat cppcheck.log; exit 1) || exit 0 261 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | HTMLCSS 2 | 3 | Copyright © 2018-2025 by Michael R Sweet 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HTMLCSS - Lightweight HTML/CSS Library 2 | ====================================== 3 | 4 | ![Version](https://img.shields.io/github/v/release/michaelrsweet/htmlcss?include_prereleases) 5 | ![Apache 2.0](https://img.shields.io/github/license/michaelrsweet/htmlcss) 6 | [![Build](https://github.com/michaelrsweet/htmlcss/workflows/Build/badge.svg)](https://github.com/michaelrsweet/htmlcss/actions/workflows/build.yml) 7 | [![Coverity Scan Status](https://img.shields.io/coverity/scan/22390.svg)](https://scan.coverity.com/projects/michaelrsweet-htmlcss) 8 | 9 | HTMLCSS is a lightweight HTML/CSS parser written in C that allows applications 10 | to prepare a HTML document for rendering or conversion. Features include: 11 | 12 | - HTML 5 markup parser 13 | - CSS 3 stylesheet parser 14 | - GIF/JPEG/PNG image file parser (metadata only) 15 | - Functions to calculate CSS properties for a given node in a HTML document 16 | - Functions to extract HTML "runs" consisting of CSS properties, content 17 | strings, and image references that can be rendered directly, including the 18 | :before and :after content from a stylesheet 19 | - A device API for reporting capabilities and handling fonts 20 | - A memory pool for efficiently storing and caching strings and CSS properties 21 | - A URL API to support fetching remote resources 22 | 23 | HTMLCSS does *not* support dynamic HTML content created using Javascript in a 24 | HTML document, as such content is typically used for interactive/dynamic web 25 | pages while HTMLCSS is intended for use with static content. 26 | 27 | 28 | Requirements 29 | ------------ 30 | 31 | HTMLCSS requires a C99 compiler like GCC, Clang, or Visual C, along with 32 | POSIX-compliant `make` and `sh` programs (Linux/Unix), Visual Studio (Windows), 33 | and/or Xcode (macOS) for doing the build. The only required library is ZLIB 34 | (1.1 or later) for compression support. 35 | 36 | 37 | Dcumentation and Examples 38 | ------------------------- 39 | 40 | Documentation can be found in the "htmlcss.html" file. 41 | 42 | 43 | Contributing Code 44 | ----------------- 45 | 46 | Code contributions should be submitted as pull requests on the Github site: 47 | 48 | http://github.com/michaelrsweet/htmlcss/pulls 49 | 50 | See the file "CONTRIBUTING.md" for more details. 51 | 52 | 53 | Legal Stuff 54 | ----------- 55 | 56 | HTMLCSS is Copyright © 2018-2025 by Michael R Sweet. 57 | 58 | HTMLCSS is licensed under the Apache License Version 2.0. See the files 59 | "LICENSE" and "NOTICE" for more information. 60 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Security Policy 2 | =============== 3 | 4 | This file describes how security issues are reported and handled, and what the 5 | expectations are for security issues reported to this project. 6 | 7 | 8 | Reporting a Security Bug 9 | ------------------------ 10 | 11 | For the purposes of this project, a security bug is a software defect that 12 | allows a *local or remote user* to gain unauthorized access or privileges on the 13 | host computer. Such defects should be reported to the project security advisory 14 | page at . 15 | 16 | > *Note:* If you've found a software defect that allows a *program* to gain 17 | > unauthorized access or privileges on the host computer or causes the program 18 | > to crash, that defect should be reported as an ordinary project issue at 19 | > . 20 | 21 | 22 | Responsible Disclosure 23 | ---------------------- 24 | 25 | With *responsible disclosure*, a security issue (and its fix) is disclosed only 26 | after a mutually-agreed period of time (the "embargo date"). The issue and fix 27 | are shared amongst and reviewed by the key stakeholders (Linux distributions, 28 | OS vendors, etc.) and the CERT/CC. Fixes are released to the public on the 29 | agreed-upon date. 30 | 31 | > Responsible disclosure applies only to production releases. A security 32 | > vulnerability that only affects unreleased code can be fixed immediately 33 | > without coordination. Vendors *should not* package and release unstable 34 | > snapshots, beta releases, or release candidates of this software. 35 | 36 | 37 | Supported Versions 38 | ------------------ 39 | 40 | All production releases of this software are subject to this security policy. A 41 | production release is tagged and given a semantic version number of the form: 42 | 43 | MAJOR.MINOR.PATCH 44 | 45 | where "MAJOR" is an integer starting at 1 and "MINOR" and "PATCH" are integers 46 | starting at 0. A feature release has a "PATCH" value of 0, for example: 47 | 48 | 1.0.0 49 | 1.1.0 50 | 2.0.0 51 | 52 | Beta releases and release candidates are *not* production releases and use 53 | semantic version numbers of the form: 54 | 55 | MAJOR.MINORbNUMBER 56 | MAJOR.MINORrcNUMBER 57 | 58 | where "MAJOR" and "MINOR" identify the new feature release version number and 59 | "NUMBER" identifies a beta or release candidate number starting at 1, for 60 | example: 61 | 62 | 1.0b1 63 | 1.0b2 64 | 1.0rc1 65 | -------------------------------------------------------------------------------- /TESTSUITE.md: -------------------------------------------------------------------------------- 1 | Notes About the Test Suite Directory 2 | ==================================== 3 | 4 | The "testsuite" directory contains files used by the `testhtmlcss` program to 5 | verify the proper operation of the HTMLCSS library. 6 | 7 | 8 | HTML and CSS Files 9 | ------------------ 10 | 11 | Most of the HTML and CSS files are my original creation. Many come from the 12 | HTMLDOC test suite. The "source-sans-pro.css" file is part of the Adobe open 13 | font project - see "Font Files" below. 14 | 15 | 16 | Image Files 17 | ----------- 18 | 19 | All of the GIF and JPEG image files are my original creation. The "logo.gif" 20 | file is the original logo of a company I founded and later closed after 17 years 21 | when I went to work for Apple. 22 | 23 | The PNG test images come from Willem van Schaik's 24 | [PngSuite](http://www.schaik.com/pngsuite/). The license for these images can 25 | be found in the file "pngsuite-LICENSE.md". 26 | 27 | The SVG test images come from several sources - see the corresponding 28 | "xxx-LICENSE.md" files. 29 | 30 | 31 | Font Files 32 | ---------- 33 | 34 | The test fonts are all from Adobe's open font project. The license for these 35 | fonts can be found in the file "Source-LICENSE.md". The selection of font files 36 | was driven by a need to test selection of different weights and styles from CSS 37 | and to test support for Unicode character ranges - the Japanese regional set is 38 | the smallest... 39 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | To-Do 2 | ===== 3 | 4 | - Refactor to use TTF library? 5 | - Add virtual device interface (thinking particularly of using PDFio, but other 6 | devices possible) for managing fonts/widths/rendering. 7 | - Update CSS and HTML code for current standards. 8 | 9 | -------------------------------------------------------------------------------- /common-private.h: -------------------------------------------------------------------------------- 1 | // 2 | // Common private header file for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #ifndef HTMLCSS_COMMON_PRIVATE_H 13 | # define HTMLCSS_COMMON_PRIVATE_H 14 | # include "common.h" 15 | # ifdef _WIN32 16 | # include 17 | # include 18 | 19 | // 20 | // Microsoft renames the POSIX functions to _name, and introduces 21 | // a broken compatibility layer using the original names. As a result, 22 | // random crashes can occur when, for example, strdup() allocates memory 23 | // from a different heap than used by malloc() and free(). 24 | // 25 | // To avoid moronic problems like this, we #define the POSIX function 26 | // names to the corresponding non-standard Microsoft names. 27 | // 28 | 29 | # define access _access 30 | # define close _close 31 | # define fileno _fileno 32 | # define lseek _lseek 33 | # define mkdir(d,p) _mkdir(d) 34 | # define open _open 35 | # define read _read 36 | # define rmdir _rmdir 37 | # define snprintf _snprintf 38 | # define strdup _strdup 39 | # define unlink _unlink 40 | # define vsnprintf _vsnprintf 41 | # define write _write 42 | 43 | // 44 | // Map various parameters for POSIX... 45 | // 46 | 47 | # define F_OK 00 48 | # define W_OK 02 49 | # define R_OK 04 50 | # define O_RDONLY _O_RDONLY 51 | # define O_WRONLY _O_WRONLY 52 | # define O_CREAT _O_CREAT 53 | # define O_TRUNC _O_TRUNC 54 | 55 | # else 56 | # include 57 | # endif // _WIN32 58 | 59 | 60 | // 61 | // DEBUG is typically defined for debug builds. _HC_DEBUG maps to fprintf when 62 | // DEBUG is defined and is a no-op otherwise... 63 | // 64 | 65 | # ifdef DEBUG 66 | # define _HC_DEBUG(...) fprintf(stderr, __VA_ARGS__) 67 | # else 68 | # define _HC_DEBUG(...) 69 | # endif // DEBUG 70 | 71 | 72 | // 73 | // _HC_FORMAT_ARGS tells the compiler to validate printf-style format 74 | // arguments, producing warnings when there are issues... 75 | // 76 | 77 | # if defined(__has_extension) || defined(__GNUC__) 78 | # define _HC_FORMAT_ARGS(a,b) __attribute__ ((__format__(__printf__, a, b))) 79 | # else 80 | # define _HC_FORMAT_ARGS(a,b) 81 | # endif // __has_extension || __GNUC__ 82 | 83 | 84 | # ifdef __cplusplus 85 | extern "C" { 86 | # endif // __cplusplus 87 | 88 | 89 | // 90 | // Types... 91 | // 92 | 93 | typedef int (*_hc_compare_func_t)(const void *, const void *); 94 | // bsearch/qsort comparison function 95 | typedef unsigned char _hc_uchar_t; // Unsigned 8-bit byte 96 | 97 | 98 | // 99 | // Functions... 100 | // 101 | 102 | extern bool _hcDefaultErrorCB(void *ctx, const char *message, int linenum); 103 | extern char *_hcDefaultURLCB(void *ctx, const char *url, char *buffer, size_t bufsize); 104 | 105 | 106 | # ifdef __cplusplus 107 | } 108 | # endif // __cplusplus 109 | #endif // !HTMLCSS_COMMON_PRIVATE_H 110 | -------------------------------------------------------------------------------- /common.c: -------------------------------------------------------------------------------- 1 | // 2 | // Common functions for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #include "common-private.h" 13 | 14 | 15 | // 16 | // '_hcDefaultErrorCB()' - Default error callback. 17 | // 18 | 19 | bool // O - `true` to continue, `false` to stop 20 | _hcDefaultErrorCB( 21 | void *ctx, // I - Context pointer (unused) 22 | const char *message, // I - Message string 23 | int linenum) // I - Line number (unused) 24 | { 25 | (void)ctx; 26 | (void)linenum; 27 | 28 | fputs(message, stderr); 29 | putc('\n', stderr); 30 | 31 | return (true); 32 | } 33 | 34 | 35 | // 36 | // '_hcDefaultURLCB()' - Default URL callback. 37 | // 38 | 39 | char * // O - Local path to URL or `NULL` 40 | _hcDefaultURLCB( 41 | void *ctx, // I - Context pointer (unused) 42 | const char *url, // I - URL or filename 43 | char *buffer, // I - Filename buffer 44 | size_t bufsize) // I - Size of filename buffer 45 | { 46 | (void)ctx; 47 | 48 | if (!access(url, R_OK)) 49 | { 50 | /* 51 | * Local file we can read... 52 | */ 53 | 54 | strncpy(buffer, url, bufsize - 1); 55 | buffer[bufsize - 1] = '\0'; 56 | } 57 | else if (!strncmp(url, "file:///", 8)) 58 | { 59 | char *bufptr, // Pointer into buffer 60 | *bufend; // End of buffer 61 | 62 | for (url += 7, bufptr = buffer, bufend = buffer + bufsize - 1; *url; url ++) 63 | { 64 | int ch = *url; // Current character 65 | 66 | if (ch == '%' && isxdigit(url[1] & 255) && isxdigit(url[2] & 255)) 67 | { 68 | /* 69 | * Percent-escaped character in URL... 70 | */ 71 | 72 | if (isdigit(url[1])) 73 | ch = (url[1] - '0') << 4; 74 | else 75 | ch = (tolower(url[1]) - 'a' + 10) << 4; 76 | 77 | if (isdigit(url[2])) 78 | ch |= (url[2] - '0'); 79 | else 80 | ch |= (tolower(url[2]) - 'a' + 10); 81 | 82 | url += 2; 83 | } 84 | 85 | if (bufptr < bufend) 86 | { 87 | *bufptr++ = (char)ch; 88 | } 89 | else 90 | { 91 | errno = E2BIG; 92 | *buffer = '\0'; 93 | return (NULL); 94 | } 95 | } 96 | 97 | *bufptr = '\0'; 98 | 99 | if (access(buffer, R_OK)) 100 | { 101 | /* 102 | * File not readable/found... 103 | */ 104 | 105 | *buffer = '\0'; 106 | return (NULL); 107 | } 108 | } 109 | else 110 | { 111 | errno = EINVAL; 112 | *buffer = '\0'; 113 | return (NULL); 114 | } 115 | 116 | return (buffer); 117 | } 118 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | // 2 | // Common header file for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #ifndef HTMLCSS_COMMON_H 13 | # define HTMLCSS_COMMON_H 14 | # include 15 | # include 16 | # include 17 | # include 18 | # include 19 | # include 20 | 21 | 22 | // 23 | // Visibility and other annotations... 24 | // 25 | 26 | # if _WIN32 27 | # define _HC_DEPRECATED(m) 28 | # define _HC_INTERNAL 29 | # define _HC_PRIVATE 30 | # define _HC_PUBLIC 31 | # define _HC_FORMAT(a,b) 32 | # define _HC_NONNULL(...) 33 | # define _HC_NORETURN 34 | # elif defined(__has_extension) || defined(__GNUC__) 35 | # define _HC_DEPRECATED(m) __attribute__ ((deprecated(m))) __attribute__ ((visibility("default"))) 36 | # define _HC_INTERNAL __attribute__ ((visibility("hidden"))) 37 | # define _HC_PRIVATE __attribute__ ((visibility("default"))) 38 | # define _HC_PUBLIC __attribute__ ((visibility("default"))) 39 | # define _HC_FORMAT(a,b) __attribute__ ((__format__(__printf__, a,b))) 40 | # define _HC_NONNULL(...) __attribute__ ((nonnull(__VA_ARGS__))) 41 | # define _HC_NORETURN __attribute__ ((noreturn)) 42 | # else 43 | # define _HC_DEPRECATED(m) 44 | # define _HC_INTERNAL 45 | # define _HC_PRIVATE 46 | # define _HC_PUBLIC 47 | # define _HC_FORMAT(a,b) 48 | # define _HC_NONNULL(...) 49 | # define _HC_NORETURN 50 | # endif // __has_extension || __GNUC__ 51 | #endif // !HTMLCSS_COMMON_H 52 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | dnl 2 | dnl Configuration script for HTMLCSS 3 | dnl 4 | dnl Copyright © 2025 by Michael R Sweet 5 | dnl 6 | dnl Licensed under Apache License v2.0. See the file "LICENSE" for more 7 | dnl information. 8 | dnl 9 | 10 | dnl *********************************************************************** 11 | dnl 12 | dnl Note: Using autoheader or automake on this project will break the HTMLCSS 13 | dnl build system. Use "autoconf -f" to regenerate the configure script if 14 | dnl you make changes to this file. 15 | dnl 16 | dnl *********************************************************************** 17 | 18 | 19 | dnl We need at least autoconf 2.70 for --runstatedir... 20 | AC_PREREQ([2.70]) 21 | 22 | 23 | dnl Package name and version... 24 | AC_INIT([htmlcss], [0.1.0], [https://github.com/michaelrsweet/htmlcss/issues], [htmlcss], [https://www.msweet.org/htmlcss]) 25 | 26 | HTMLCSS_VERSION="AC_PACKAGE_VERSION" 27 | HTMLCSS_VERSION_MAJOR="`echo AC_PACKAGE_VERSION | awk -F. '{print $1}'`" 28 | HTMLCSS_VERSION_MINOR="`echo AC_PACKAGE_VERSION | awk -F. '{printf("%d\n",$2);}'`" 29 | AC_SUBST([HTMLCSS_VERSION]) 30 | AC_SUBST([HTMLCSS_VERSION_MAJOR]) 31 | AC_SUBST([HTMLCSS_VERSION_MINOR]) 32 | 33 | 34 | dnl This line is provided to ensure that you don't run the autoheader program 35 | dnl against this project. Doing so is completely unsupported and WILL cause 36 | dnl problems! 37 | AH_TOP([#error "Somebody ran autoheader on this project which is unsupported and WILL cause problems."]) 38 | 39 | 40 | dnl Get the build and host platforms and split the host_os value 41 | AC_CANONICAL_BUILD 42 | AC_CANONICAL_HOST 43 | 44 | [host_os_name="$(echo $host_os | sed -e '1,$s/[0-9.]*$//g')"] 45 | [host_os_version="$(echo $host_os | sed -e '1,$s/^[^0-9.]*//g' | awk -F. '{print $1 $2}')"] 46 | # Linux often does not yield an OS version we can use... 47 | AS_IF([test "x$host_os_version" = x], [ 48 | host_os_version="0" 49 | ]) 50 | 51 | 52 | dnl Compiler options... 53 | CFLAGS="${CFLAGS:=}" 54 | CPPFLAGS="${CPPFLAGS:=}" 55 | DSOFLAGS="${DSOFLAGS:=}" 56 | LDFLAGS="${LDFLAGS:=}" 57 | LIBS="${LIBS:=}" 58 | OPTIM="${OPTIM:=}" 59 | 60 | AC_SUBST([DSOFLAGS]) 61 | AC_SUBST([OPTIM]) 62 | 63 | 64 | dnl Standard programs... 65 | AC_PROG_CC 66 | AC_PROG_RANLIB 67 | AC_PATH_PROG([AR], [ar]) 68 | AC_PATH_PROGS([CODE_SIGN], [codesign true]) 69 | AC_PATH_PROG([MKDIR], [mkdir]) 70 | AC_PATH_PROG([RM], [rm]) 71 | AC_PATH_PROG([RMDIR], [rmdir]) 72 | AC_PATH_PROG([LN], [ln]) 73 | 74 | 75 | dnl Figure out the correct "ar" command flags... 76 | AS_IF([test "$ac_cv_prog_ranlib" = ":"], [ 77 | ARFLAGS="crs" 78 | ], [ 79 | ARFLAGS="cr" 80 | ]) 81 | AC_SUBST([ARFLAGS]) 82 | 83 | 84 | dnl install-sh 85 | AC_MSG_CHECKING([for install-sh script]) 86 | INSTALL="$(pwd)/install-sh" 87 | AC_SUBST([INSTALL]) 88 | AC_MSG_RESULT([using $INSTALL]) 89 | 90 | 91 | dnl Check for pkg-config, which is used for some other tests later on... 92 | AC_PATH_TOOL([PKGCONFIG], [pkg-config]) 93 | 94 | PKGCONFIG_CFLAGS="-I\${includedir}" 95 | PKGCONFIG_LIBS="-L\${libdir} -lhtmlcss" 96 | PKGCONFIG_LIBS_PRIVATE="-lm" 97 | PKGCONFIG_REQUIRES="zlib" 98 | AC_SUBST([PKGCONFIG_CFLAGS]) 99 | AC_SUBST([PKGCONFIG_LIBS]) 100 | AC_SUBST([PKGCONFIG_LIBS_PRIVATE]) 101 | AC_SUBST([PKGCONFIG_REQUIRES]) 102 | 103 | 104 | dnl ZLIB 105 | AC_MSG_CHECKING([for zlib via pkg-config]) 106 | AS_IF([$PKGCONFIG --exists zlib], [ 107 | AC_MSG_RESULT([yes]) 108 | CPPFLAGS="$($PKGCONFIG --cflags zlib) $CPPFLAGS" 109 | LIBS="$($PKGCONFIG --libs zlib) $LIBS" 110 | ],[ 111 | AC_MSG_RESULT([no]) 112 | AC_CHECK_HEADER([zlib.h]) 113 | AC_CHECK_LIB([z], [inflateCopy]) 114 | 115 | AS_IF([test x$ac_cv_header_zlib_h != xyes -o x$ac_cv_lib_z_inflateCopy != xyes], [ 116 | AC_MSG_ERROR([Sorry, this software requires zlib 1.1 or higher.]) 117 | ]) 118 | 119 | PKGCONFIG_REQUIRES="" 120 | PKGCONFIG_LIBS_PRIVATE="-lz $PKGCONFIG_LIBS_PRIVATE" 121 | ]) 122 | 123 | 124 | dnl Library target... 125 | AC_ARG_ENABLE([static], AS_HELP_STRING([--disable-static], [do not install static library])) 126 | AC_ARG_ENABLE([shared], AS_HELP_STRING([--enable-shared], [install shared library])) 127 | 128 | AS_IF([test x$enable_shared = xyes], [ 129 | AS_IF([test "$host_os_name" = darwin], [ 130 | LIBHTMLCSS="libhtmlcss.1.dylib" 131 | ], [ 132 | LIBHTMLCSS="libhtmlcss.so.1" 133 | ]) 134 | 135 | AS_IF([test x$enable_static != xno], [ 136 | LIBHTMLCSS_STATIC="libhtmlcss.a" 137 | ], [ 138 | LIBHTMLCSS_STATIC="" 139 | ]) 140 | ], [ 141 | LIBHTMLCSS="libhtmlcss.a" 142 | LIBHTMLCSS_STATIC="" 143 | PKGCONFIG_LIBS="$PKGCONFIG_LIBS $PKGCONFIG_LIBS_PRIVATE" 144 | PKGCONFIG_LIBS_PRIVATE="" 145 | ]) 146 | 147 | AC_SUBST([LIBHTMLCSS]) 148 | AC_SUBST([LIBHTMLCSS_STATIC]) 149 | 150 | 151 | dnl Extra compiler options... 152 | AC_ARG_ENABLE([debug], AS_HELP_STRING([--enable-debug], [turn on debugging, default=no])) 153 | AC_ARG_ENABLE([maintainer], AS_HELP_STRING([--enable-maintainer], [turn on maintainer mode, default=no])) 154 | AC_ARG_ENABLE([sanitizer], AS_HELP_STRING([--enable-sanitizer], [build with AddressSanitizer, default=no])) 155 | 156 | AS_IF([test x$enable_debug = xyes], [ 157 | OPTIM="$OPTIM -g" 158 | CSFLAGS="" 159 | ], [ 160 | OPTIM="$OPTIM -g -Os" 161 | CSFLAGS="-o runtime" 162 | ]) 163 | 164 | AC_SUBST([CSFLAGS]) 165 | 166 | WARNINGS="" 167 | AC_SUBST([WARNINGS]) 168 | 169 | AS_IF([test -n "$GCC"], [ 170 | AS_IF([test x$enable_sanitizer = xyes], [ 171 | # Use -fsanitize=address with debugging... 172 | OPTIM="$OPTIM -fsanitize=address" 173 | ], [ 174 | # Otherwise use the Fortify enhancements to catch any unbounded 175 | # string operations... 176 | CPPFLAGS="$CPPFLAGS -D_FORTIFY_SOURCE=2" 177 | ]) 178 | 179 | dnl Show all standard warnings + unused variables when compiling... 180 | WARNINGS="-Wall -Wunused" 181 | 182 | dnl Drop some not-useful/unreliable warnings... 183 | for warning in char-subscripts format-truncation format-y2k switch unused-result; do 184 | AC_MSG_CHECKING([whether compiler supports -Wno-$warning]) 185 | 186 | OLDCFLAGS="$CFLAGS" 187 | CFLAGS="$CFLAGS -Wno-$warning -Werror" 188 | 189 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM()], [ 190 | AC_MSG_RESULT(yes) 191 | WARNINGS="$WARNINGS -Wno-$warning" 192 | ], [ 193 | AC_MSG_RESULT(no) 194 | ]) 195 | 196 | CFLAGS="$OLDCFLAGS" 197 | done 198 | 199 | dnl Maintainer mode enables -Werror... 200 | AS_IF([test x$enable_maintainer = xyes], [ 201 | WARNINGS="$WARNINGS -Werror -Wno-error=deprecated" 202 | ]) 203 | 204 | dnl See if PIE options are supported... 205 | AC_MSG_CHECKING(whether compiler supports -fPIE) 206 | OLDCFLAGS="$CFLAGS" 207 | AS_CASE(["$host_os_name"], 208 | [darwin*], [ 209 | CFLAGS="$CFLAGS -fPIC -fPIE -Wl,-pie" 210 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[]])],[ 211 | OLDCFLAGS="-fPIC $OLDCFLAGS" 212 | LDFLAGS="-fPIE -Wl,-pie $LDFLAGS" 213 | AC_MSG_RESULT(yes) 214 | ],[ 215 | AC_MSG_RESULT(no) 216 | ]) 217 | ], [*], [ 218 | CFLAGS="$CFLAGS -fPIC -fPIE -pie" 219 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[]])],[ 220 | OLDCFLAGS="-fPIC $OLDCFLAGS" 221 | LDFLAGS="-fPIE -pie $LDFLAGS" 222 | AC_MSG_RESULT(yes) 223 | ],[ 224 | AC_MSG_RESULT(no) 225 | ]) 226 | ]) 227 | CFLAGS="$OLDCFLAGS" 228 | 229 | dnl OS-specific compiler options... 230 | AC_MSG_CHECKING([for OS-specific compiler options]) 231 | AS_CASE(["$host_os_name"], [linux*], [ 232 | # Make sure we get the full set of 64-bit Linux APIs from the headers... 233 | CPPFLAGS="$CPPFLAGS -D__USE_MISC -D_GNU_SOURCE -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64" 234 | 235 | # Mark read-only sections as relocatable to random addresses... 236 | LDFLAGS="$LDFLAGS -Wl,-z,relro,-z,now" 237 | 238 | AC_MSG_RESULT([-D__USE_MISC -D_GNU_SOURCE -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64 -Wl,-z,relro,-z,now]) 239 | ], [darwin*], [ 240 | # When not building for debug, target macOS 11 or later, "universal" 241 | # binaries when possible... 242 | AS_IF([echo "$CPPFLAGS $CFLAGS $LDFLAGS $OPTIM" | grep -q "\\-arch "], [ 243 | # Don't add architecture/min-version flags if they are already present 244 | AC_MSG_RESULT([none]) 245 | ], [echo "$CPPFLAGS $CFLAGS $LDFLAGS $OPTIM" | grep -q "\\-mmacosx-version-"], [ 246 | # Don't add architecture/min-version flags if they are already present 247 | AC_MSG_RESULT([none]) 248 | ], [test "$host_os_version" -ge 200 -a x$enable_debug != xyes], [ 249 | # macOS 11.0 and higher support the Apple Silicon (arm64) CPUs 250 | OPTIM="$OPTIM -mmacosx-version-min=11.0 -arch x86_64 -arch arm64" 251 | AC_MSG_RESULT([-mmacosx-version-min=11.0 -arch x86_64 -arch arm64]) 252 | ], [ 253 | # Don't add architecture/min-version flags if debug enabled 254 | AC_MSG_RESULT([none]) 255 | ]) 256 | ], [*], [ 257 | AC_MSG_RESULT([none]) 258 | ]) 259 | ]) 260 | 261 | 262 | dnl Extra linker options... 263 | AC_ARG_WITH([dsoflags], AS_HELP_STRING([--with-dsoflags=...], [Specify additional DSOFLAGS]), [ 264 | DSOFLAGS="$withval $DSOFLAGS" 265 | ]) 266 | AC_ARG_WITH([ldflags], AS_HELP_STRING([--with-ldflags=...], [Specify additional LDFLAGS]), [ 267 | LDFLAGS="$withval $LDFLAGS" 268 | ]) 269 | 270 | 271 | dnl Generate the Makefile and pkg-config file... 272 | AC_CONFIG_FILES([Makefile htmlcss.pc]) 273 | AC_OUTPUT 274 | -------------------------------------------------------------------------------- /css-core.c: -------------------------------------------------------------------------------- 1 | // 2 | // CSS import functions for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | # include "css-private.h" 13 | # include "pool.h" 14 | 15 | 16 | // 17 | // 'hcCSSDelete()' - Free memory associated with a stylesheet. 18 | // 19 | 20 | void 21 | hcCSSDelete(hc_css_t *css) // I - Stylesheet 22 | { 23 | hc_element_t element; // Current element 24 | 25 | 26 | if (!css) 27 | return; 28 | 29 | for (element = HC_ELEMENT_WILDCARD; element < HC_ELEMENT_MAX; element ++) 30 | _hcRuleColClear(css->rules + element, 0); 31 | _hcRuleColClear(&css->all_rules, 1); 32 | 33 | free(css); 34 | } 35 | 36 | 37 | // 38 | // 'hcCSSNew()' - Allocate a new stylesheet. 39 | // 40 | 41 | hc_css_t * // O - Stylesheet 42 | hcCSSNew(hc_pool_t *pool) // I - Memory pool 43 | { 44 | hc_css_t *css = (hc_css_t *)calloc(1, sizeof(hc_css_t)); 45 | // Stylesheet 46 | 47 | 48 | if (css) 49 | { 50 | // Set defaults based on "universal" size (intersection of US Letter and 51 | // ISO A4) at 128ppi. 52 | css->pool = pool; 53 | 54 | hcCSSSetMedia(css, "print", 24, 8, 1058.27f, 1408.0f); 55 | } 56 | 57 | return (css); 58 | } 59 | 60 | 61 | // 62 | // 'hcCSSSetMedia()' - Set the base media settings. 63 | // 64 | 65 | int // O - 1 on success, 0 on failure 66 | hcCSSSetMedia( 67 | hc_css_t *css, // I - Stylesheet 68 | const char *type, // I - Media type ("print', etc.) 69 | int color_bits, // I - Bits of color supported 70 | int monochrome_bits, // I - Bits of grayscale supported 71 | float width, // I - Device width 72 | float height) // I - Device height 73 | { 74 | if (!css || !type || color_bits < 0 || monochrome_bits < 0 || width <= 0.0f || height <= 0.0f) 75 | return (0); 76 | 77 | css->media.type = hcPoolGetString(css->pool, type); 78 | css->media.color_bits = color_bits; 79 | css->media.monochrome_bits = monochrome_bits; 80 | css->media.size.width = width; 81 | css->media.size.height = height; 82 | 83 | return (0); 84 | } 85 | -------------------------------------------------------------------------------- /css-private.h: -------------------------------------------------------------------------------- 1 | // 2 | // Private CSS header file for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #ifndef HTMLCSS_CSS_PRIVATE_H 13 | # define HTMLCSS_CSS_PRIVATE_H 14 | # include "css.h" 15 | # include "html-private.h" 16 | # include "sha3.h" 17 | # ifdef __cplusplus 18 | extern "C" { 19 | # endif // __cplusplus 20 | 21 | 22 | // 23 | // Types... 24 | // 25 | 26 | // 27 | // CSS selectors are linked lists starting at the leaf node to speed lookups. 28 | // Each selector is a sequence of matching statements starting with an 29 | // associated element (* wildcard, P, etc.) followed by zero or more additional 30 | // matching statements (".classname", "#identifier", ":link", etc.) 31 | // 32 | // A list of selectors is associated with an array of properties, each of which 33 | // is a simple key/value pair. This association is called a rule set. 34 | // 35 | // For convenience and lookup efficiency, rule sets with compound selectors, 36 | // e.g.: 37 | // 38 | // h1, h2, h3 { font-weight: bold; } 39 | // 40 | // are split into three separate rule sets using each of the selector lists. 41 | // 42 | 43 | typedef enum _hc_match_e 44 | { 45 | _HC_MATCH_ATTR_EXIST, // [NAME] 46 | _HC_MATCH_ATTR_EQUALS, // [NAME=VALUE] 47 | _HC_MATCH_ATTR_CONTAINS, // [NAME*=VALUE] 48 | _HC_MATCH_ATTR_BEGINS, // [NAME^=VALUE] 49 | _HC_MATCH_ATTR_ENDS, // [NAME$=VALUE] 50 | _HC_MATCH_ATTR_LANG, // [NAME|=VALUE] (language/prefix match) 51 | _HC_MATCH_ATTR_SPACE, // [NAME~=VALUE] (space-delimited value match) 52 | _HC_MATCH_CLASS, // .NAME 53 | _HC_MATCH_ID, // #NAME 54 | _HC_MATCH_PSEUDO_CLASS // :NAME or :NANE(VALUE) pseudo-class 55 | } _hc_match_t; 56 | 57 | typedef enum _hc_relation_e // Relationship to previous selector 58 | { 59 | _HC_RELATION_CHILD, // Child (descendent) of previous (E F) 60 | _HC_RELATION_IMMED_CHILD, // Immediate child of previous (E > F) 61 | _HC_RELATION_SIBLING, // Sibling of previous (E ~ F) 62 | _HC_RELATION_IMMED_SIBLING // Immediate sibling of previous (E + F) 63 | } _hc_relation_t; 64 | 65 | typedef struct _hc_css_selstmt_s // CSS selector matching statements 66 | { 67 | _hc_match_t match; // Matching rule 68 | const char *name, // Name, if needed 69 | *value; // Value, if needed 70 | } _hc_css_selstmt_t; 71 | 72 | typedef struct _hc_css_sel_s // CSS selector 73 | { 74 | struct _hc_css_sel_s *prev; // Previous selector 75 | hc_element_t element; // Element 76 | _hc_relation_t relation; // Relation to previous 77 | size_t num_stmts; // Number of selector matching statements 78 | _hc_css_selstmt_t *stmts; // Matching statements 79 | } _hc_css_sel_t; 80 | 81 | typedef struct _hc_rule_s // CSS rule set 82 | { 83 | hc_sha3_256_t hash; // Hash of selector 84 | _hc_css_sel_t *sel; // Leaf selector 85 | hc_dict_t *props; // Properties 86 | } _hc_rule_t; 87 | 88 | typedef struct _hc_rulecol_s // Collection of rules 89 | { 90 | int needs_sort; // Needs sorting? 91 | size_t alloc_rules; // Allocated rules 92 | size_t num_rules; // Number of rules 93 | _hc_rule_t **rules; // Rules 94 | } _hc_rulecol_t; 95 | 96 | struct _hc_css_s 97 | { 98 | hc_pool_t *pool; // Memory pool 99 | hc_media_t media; // Base media definition 100 | _hc_rulecol_t all_rules; // All rule sets in the stylesheet and document 101 | _hc_rulecol_t rules[HC_ELEMENT_MAX]; 102 | // Rule sets organized by element 103 | }; 104 | 105 | 106 | // 107 | // Functions... 108 | // 109 | 110 | extern void _hcCSSImportString(hc_css_t *css, hc_dict_t *props, const char *s); 111 | extern void _hcCSSSelAddStmt(hc_css_t *css, _hc_css_sel_t *sel, _hc_match_t match, const char *name, const char *value); 112 | extern void _hcCSSSelDelete(_hc_css_sel_t *sel); 113 | extern void _hcCSSSelHash(_hc_css_sel_t *sel, hc_sha3_256_t hash); 114 | extern _hc_css_sel_t *_hcCSSSelNew(hc_css_t *css, _hc_css_sel_t *prev, hc_element_t element, _hc_relation_t rel); 115 | 116 | extern void _hcRuleColAdd(hc_css_t *css, _hc_rulecol_t *col, _hc_rule_t *rule); 117 | extern void _hcRuleColClear(_hc_rulecol_t *col, int delete_rules); 118 | extern _hc_rule_t *_hcRuleColFindHash(_hc_rulecol_t *col, const hc_sha3_256_t hash); 119 | extern void _hcRuleDelete(_hc_rule_t *rule); 120 | extern _hc_rule_t *_hcRuleNew(hc_css_t *css, const hc_sha3_256_t hash, _hc_css_sel_t *sel, hc_dict_t *props); 121 | 122 | 123 | # ifdef __cplusplus 124 | } 125 | # endif // __cplusplus 126 | #endif // !HTMLCSS_CSS_PRIVATE_H 127 | -------------------------------------------------------------------------------- /css-rule.c: -------------------------------------------------------------------------------- 1 | // 2 | // CSS rule set support functions for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | # include "css-private.h" 13 | # include "pool-private.h" 14 | 15 | 16 | // 17 | // Local functions... 18 | // 19 | 20 | static int hc_compare_rules(_hc_rule_t **a, _hc_rule_t **b); 21 | 22 | 23 | // 24 | // '_hcCSSSelAddStmt()' - Add a matching statement to a selector. 25 | // 26 | 27 | void 28 | _hcCSSSelAddStmt(hc_css_t *css, // I - Stylesheet 29 | _hc_css_sel_t *sel, // I - Selector 30 | _hc_match_t match, // I - Match type 31 | const char *name, // I - Name, if any 32 | const char *value) // I - Value, if any 33 | { 34 | _hc_css_selstmt_t *temp; // Current statement 35 | 36 | 37 | if ((temp = realloc(sel->stmts, (sel->num_stmts + 1) * sizeof(_hc_css_selstmt_t))) != NULL) 38 | { 39 | sel->stmts = temp; 40 | temp += sel->num_stmts; 41 | sel->num_stmts ++; 42 | 43 | temp->match = match; 44 | temp->name = hcPoolGetString(css->pool, name); 45 | temp->value = hcPoolGetString(css->pool, value); 46 | } 47 | else 48 | _hcPoolError(css->pool, 0, "Unable to allocate memory for selector statement."); 49 | } 50 | 51 | 52 | // 53 | // '_hcCSSSelDelete()' - Free the memory used by a list of selectors. 54 | // 55 | 56 | void 57 | _hcCSSSelDelete(_hc_css_sel_t *sel) // I - Selectors 58 | { 59 | _hc_css_sel_t *prev; // Previous selector 60 | 61 | 62 | while (sel) 63 | { 64 | prev = sel->prev; 65 | 66 | if (sel->stmts) 67 | free(sel->stmts); 68 | 69 | free(sel); 70 | 71 | sel = prev; 72 | } 73 | } 74 | 75 | 76 | // 77 | // '_hcCSSSelHash()' - Create a SHA3-256 hash of a list of selectors. 78 | // 79 | 80 | void 81 | _hcCSSSelHash(_hc_css_sel_t *sel, // I - Selectors 82 | hc_sha3_256_t hash) // O - Hash of selectors 83 | { 84 | hc_sha3_t ctx; // SHA3 hashing context 85 | 86 | 87 | hcSHA3Init(&ctx); 88 | 89 | while (sel) 90 | { 91 | hcSHA3Update(&ctx, &sel->element, sizeof(sel->element)); 92 | hcSHA3Update(&ctx, &sel->relation, sizeof(sel->relation)); 93 | if (sel->stmts) 94 | hcSHA3Update(&ctx, sel->stmts, sel->num_stmts * sizeof(_hc_css_selstmt_t)); 95 | 96 | sel = sel->prev; 97 | } 98 | 99 | hcSHA3Final(&ctx, hash, HC_SHA3_256_SIZE); 100 | } 101 | 102 | 103 | // 104 | // '_hcCSSSelNew()' - Create a new CSS selector. 105 | // 106 | 107 | _hc_css_sel_t * // O - New selector 108 | _hcCSSSelNew(hc_css_t *css, // I - Stylesheet 109 | _hc_css_sel_t *prev, // I - Previous selector, if any 110 | hc_element_t element, // I - Element or `HD_ELEMENT_WILDCARD` 111 | _hc_relation_t rel) // I - Relation to previous selector 112 | { 113 | _hc_css_sel_t *sel; // New selector 114 | 115 | 116 | if ((sel = (_hc_css_sel_t *)calloc(1, sizeof(_hc_css_sel_t))) != NULL) 117 | { 118 | sel->prev = prev; 119 | sel->element = element; 120 | sel->relation = rel; 121 | } 122 | else 123 | _hcPoolError(css->pool, 0, "Unable to allocate memory for selector."); 124 | 125 | return (sel); 126 | } 127 | 128 | 129 | // 130 | // '_hcRuleColAdd()' - Add a rule set to a collection. 131 | // 132 | 133 | void 134 | _hcRuleColAdd(hc_css_t *css, // I - Stylesheet 135 | _hc_rulecol_t *col, // I - Rule set collection 136 | _hc_rule_t *rule) // I - Rule set to add 137 | { 138 | _hc_rule_t **ptr; // New rule array pointer 139 | 140 | 141 | if (col->num_rules >= col->alloc_rules) 142 | { 143 | size_t alloc_rules; // New allocation 144 | 145 | if (col->alloc_rules == 0) 146 | alloc_rules = 1; 147 | else if (col->alloc_rules < 32) 148 | alloc_rules = col->alloc_rules * 2; 149 | else 150 | alloc_rules = col->alloc_rules + 32; 151 | 152 | if ((ptr = realloc(col->rules, alloc_rules * sizeof(_hc_rule_t *))) == NULL) 153 | { 154 | _hcPoolError(css->pool, 0, "Unable to allocate memory for selector rules."); 155 | return; 156 | } 157 | 158 | col->rules = ptr; 159 | col->alloc_rules = alloc_rules; 160 | } 161 | 162 | col->rules[col->num_rules] = rule; 163 | col->num_rules ++; 164 | 165 | if (col->num_rules > 1) 166 | col->needs_sort = 1; 167 | } 168 | 169 | 170 | // 171 | // '_hcRuleColClear()' - Empty a collection, optionally freeing the rule sets in it. 172 | // 173 | 174 | void 175 | _hcRuleColClear( 176 | _hc_rulecol_t *col, // I - Rule set collection 177 | int delete_rules) // I - 1 to delete rules, 0 to just clear collection 178 | { 179 | if (delete_rules && col->num_rules) 180 | { 181 | size_t i; // Looping var 182 | _hc_rule_t **ptr; // Pointer into array 183 | 184 | for (i = col->num_rules, ptr = col->rules; i > 0; i --, ptr ++) 185 | _hcRuleDelete(*ptr); 186 | } 187 | 188 | col->num_rules = 0; 189 | col->needs_sort = 0; 190 | 191 | if (col->alloc_rules > 0) 192 | { 193 | free(col->rules); 194 | col->alloc_rules = 0; 195 | col->rules = NULL; 196 | } 197 | } 198 | 199 | 200 | // 201 | // '_hcRuleColFindHash()' - Find a rule set using its hash. 202 | // 203 | 204 | _hc_rule_t * // O - Matching rule or `NULL` 205 | _hcRuleColFindHash( 206 | _hc_rulecol_t *col, // I - Rule set collection 207 | const hc_sha3_256_t hash) // I - SHA3-256 hash 208 | { 209 | _hc_rule_t key, // Search key 210 | *ptr, // Pointer to key 211 | **match; // Matching rule 212 | 213 | 214 | if (col->needs_sort) 215 | { 216 | qsort(col->rules, col->num_rules, sizeof(_hc_rule_t *), (_hc_compare_func_t)hc_compare_rules); 217 | col->needs_sort = 0; 218 | } 219 | 220 | memcpy(key.hash, hash, sizeof(key.hash)); 221 | ptr = &key; 222 | 223 | if ((match = bsearch(&ptr, col->rules, col->num_rules, sizeof(_hc_rule_t *), (int (*)(const void *, const void *))hc_compare_rules)) != NULL) 224 | return (*match); 225 | else 226 | return (NULL); 227 | } 228 | 229 | 230 | // 231 | // '_hcRuleDelete()' - Free memory used by a rule set. 232 | // 233 | 234 | void 235 | _hcRuleDelete(_hc_rule_t *rule) // I - Rule set 236 | { 237 | _hcCSSSelDelete(rule->sel); 238 | hcDictDelete(rule->props); 239 | free(rule); 240 | } 241 | 242 | 243 | // 244 | // '_hcRuleNew()' - Create a new rule set. 245 | // 246 | 247 | _hc_rule_t * // I - Rule set 248 | _hcRuleNew( 249 | hc_css_t *css, // I - Stylesheet 250 | const hc_sha3_256_t hash, // I - SHA3-256 hash 251 | _hc_css_sel_t *sel, // I - Selectors 252 | hc_dict_t *props) // I - Properties dictionary 253 | { 254 | _hc_rule_t *rule; // New rule 255 | 256 | 257 | if ((rule = calloc(1, sizeof(_hc_rule_t))) != NULL) 258 | { 259 | memcpy(rule->hash, hash, sizeof(rule->hash)); 260 | rule->sel = sel; 261 | rule->props = hcDictCopy(props); 262 | } 263 | else 264 | _hcPoolError(css->pool, 0, "Unable to allocate memory for selector rules."); 265 | 266 | return (rule); 267 | } 268 | 269 | 270 | // 271 | // 'hc_compare_rules()' - Compare two rules in a collection. 272 | // 273 | 274 | static int // O - Result of comparison 275 | hc_compare_rules(_hc_rule_t **a, // I - First rule 276 | _hc_rule_t **b) // I - Second rule 277 | { 278 | return (memcmp((*a)->hash, (*b)->hash, sizeof((*a)->hash))); 279 | } 280 | -------------------------------------------------------------------------------- /css.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSS header file for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #ifndef HTMLCSS_CSS_H 13 | # define HTMLCSS_CSS_H 14 | # include "dict.h" 15 | # include "file.h" 16 | # include "font.h" 17 | # ifdef __cplusplus 18 | extern "C" { 19 | # endif // __cplusplus 20 | 21 | 22 | // 23 | // Constants... 24 | // 25 | 26 | # define HC_LENGTH_AUTO -999999999.0f 27 | // Automatic length 28 | 29 | 30 | // 31 | // Types... 32 | // 33 | 34 | typedef enum 35 | { 36 | HC_BACKGROUND_ATTACHMENT_SCROLL, 37 | HC_BACKGROUND_ATTACHMENT_FIXED 38 | } hc_background_attachment_t; 39 | 40 | typedef enum 41 | { 42 | HC_BACKGROUND_BOX_BORDER_BOX, 43 | HC_BACKGROUND_BOX_PADDING_BOX, 44 | HC_BACKGROUND_BOX_CONTENT_BOX 45 | } hc_background_box_t; 46 | 47 | typedef enum 48 | { 49 | HC_BACKGROUND_REPEAT_NO_REPEAT, 50 | HC_BACKGROUND_REPEAT_REPEAT, 51 | HC_BACKGROUND_REPEAT_REPEAT_X, 52 | HC_BACKGROUND_REPEAT_REPEAT_Y 53 | } hc_background_repeat_t; 54 | 55 | typedef enum 56 | { 57 | HC_BORDER_COLLAPSE_SEPARATE, 58 | HC_BORDER_COLLAPSE_COLLAPSE 59 | } hc_border_collapse_t; 60 | 61 | typedef enum 62 | { 63 | HC_BORDER_IMAGE_REPEAT_STRETCH, 64 | HC_BORDER_IMAGE_REPEAT_REPEAT, 65 | HC_BORDER_IMAGE_REPEAT_ROUND, 66 | HC_BORDER_IMAGE_REPEAT_SPACE 67 | } hc_border_image_repeat_t; 68 | 69 | 70 | typedef enum 71 | { 72 | HC_BORDER_STYLE_HIDDEN, 73 | HC_BORDER_STYLE_NONE, 74 | HC_BORDER_STYLE_DOTTED, 75 | HC_BORDER_STYLE_DASHED, 76 | HC_BORDER_STYLE_SOLID, 77 | HC_BORDER_STYLE_DOUBLE, 78 | HC_BORDER_STYLE_GROOVE, 79 | HC_BORDER_STYLE_RIDGE, 80 | HC_BORDER_STYLE_INSET, 81 | HC_BORDER_STYLE_OUTSET 82 | } hc_border_style_t; 83 | 84 | typedef enum 85 | { 86 | HC_BREAK_AUTO, // Break as needed 87 | HC_BREAK_ALWAYS, // Always break 88 | HC_BREAK_AVOID, // Avoid a break 89 | HC_BREAK_LEFT, // Break to the next left-hand page 90 | HC_BREAK_RIGHT // Break to the next right-hand page 91 | } hc_break_t; 92 | 93 | typedef enum 94 | { 95 | HC_CAPTION_SIDE_TOP, 96 | HC_CAPTION_SIDE_BOTTOM 97 | } hc_caption_side_t; 98 | 99 | typedef enum 100 | { 101 | HC_DIRECTION_LTR, // Left to right 102 | HC_DIRECTION_RTL // Right to left 103 | } hc_direction_t; 104 | 105 | typedef enum 106 | { 107 | HC_DISPLAY_NONE, 108 | HC_DISPLAY_BLOCK, 109 | HC_DISPLAY_INLINE, 110 | HC_DISPLAY_INLINE_BLOCK, 111 | HC_DISPLAY_INLINE_TABLE, 112 | HC_DISPLAY_LIST_ITEM, 113 | HC_DISPLAY_TABLE, 114 | HC_DISPLAY_TABLE_CAPTION, 115 | HC_DISPLAY_TABLE_HEADER_GROUP, 116 | HC_DISPLAY_TABLE_FOOTER_GROUP, 117 | HC_DISPLAY_TABLE_ROW_GROUP, 118 | HC_DISPLAY_TABLE_ROW, 119 | HC_DISPLAY_TABLE_COLUMN_GROUP, 120 | HC_DISPLAY_TABLE_COLUMN, 121 | HC_DISPLAY_TABLE_CELL 122 | } hc_display_t; 123 | 124 | typedef enum 125 | { 126 | HC_EMPTY_CELLS_HIDE, 127 | HC_EMPTY_CELLS_SHOW 128 | } hc_empty_cells_t; 129 | 130 | typedef enum 131 | { 132 | HC_FLOAT_NONE, 133 | HC_FLOAT_LEFT, 134 | HC_FLOAT_RIGHT 135 | } hc_float_t; 136 | 137 | typedef enum 138 | { 139 | HC_LIST_STYLE_POSITION_INSIDE, 140 | HC_LIST_STYLE_POSITION_OUTSIDE 141 | } hc_list_style_position_t; 142 | 143 | typedef enum 144 | { 145 | HC_LIST_STYLE_TYPE_DISC, 146 | HC_LIST_STYLE_TYPE_CIRCLE, 147 | HC_LIST_STYLE_TYPE_SQUARE, 148 | HC_LIST_STYLE_TYPE_DECIMAL, 149 | HC_LIST_STYLE_TYPE_DECIMAL_LEADING_ZERO, 150 | HC_LIST_STYLE_TYPE_LOWER_ROMAN, 151 | HC_LIST_STYLE_TYPE_UPPER_ROMAN, 152 | HC_LIST_STYLE_TYPE_LOWER_GREEK, 153 | HC_LIST_STYLE_TYPE_LOWER_LATIN, 154 | HC_LIST_STYLE_TYPE_UPPER_LATIN, 155 | HC_LIST_STYLE_TYPE_ARMENIAN, 156 | HC_LIST_STYLE_TYPE_GEORGIAN, 157 | HC_LIST_STYLE_TYPE_LOWER_ALPHA, 158 | HC_LIST_STYLE_TYPE_UPPER_ALPHA, 159 | HC_LIST_STYLE_TYPE_NONE 160 | } hc_list_style_type_t; 161 | 162 | typedef enum 163 | { 164 | HC_OVERFLOW_HIDDEN, 165 | HC_OVERFLOW_VISIBLE, 166 | HC_OVERFLOW_SCROLL, 167 | HC_OVERFLOW_AUTO 168 | } hc_overflow_t; 169 | 170 | typedef enum 171 | { 172 | HC_TABLE_LAYOUT_AUTO, 173 | HC_TABLE_LAYOUT_FIXED 174 | } hc_table_layout_t; 175 | 176 | typedef enum 177 | { 178 | HC_TEXT_ALIGN_LEFT, 179 | HC_TEXT_ALIGN_RIGHT, 180 | HC_TEXT_ALIGN_CENTER, 181 | HC_TEXT_ALIGN_JUSTIFY 182 | } hc_text_align_t; 183 | 184 | typedef enum 185 | { 186 | HC_TEXT_DECORATION_NONE, 187 | HC_TEXT_DECORATION_UNDERLINE, 188 | HC_TEXT_DECORATION_OVERLINE, 189 | HC_TEXT_DECORATION_LINE_THROUGH 190 | } hc_text_decoration_t; 191 | 192 | typedef enum 193 | { 194 | HC_TEXT_TRANSFORM_NONE, 195 | HC_TEXT_TRANSFORM_CAPITALIZE, 196 | HC_TEXT_TRANSFORM_LOWERCASE, 197 | HC_TEXT_TRANSFORM_UPPERCASE 198 | } hc_text_transform_t; 199 | 200 | typedef enum 201 | { 202 | HC_UNICODE_BIDI_NORMAL, 203 | HC_UNICODE_BIDI_EMBED, 204 | HC_UNICODE_BIDI_OVERRIDE 205 | } hc_unicode_bidi_t; 206 | 207 | typedef enum 208 | { 209 | HC_WHITE_SPACE_NORMAL, 210 | HC_WHITE_SPACE_NOWRAP, 211 | HC_WHITE_SPACE_PRE, 212 | HC_WHITE_SPACE_PRE_LINE, 213 | HC_WHITE_SPACE_PRE_WRAP 214 | } hc_white_space_t; 215 | 216 | typedef struct hc_color_s // sRGBA color 217 | { 218 | float red; // Red, 0.0 to 1.0 219 | float green; // Green, 0.0 to 1.0 220 | float blue; // Blue, 0.0 to 1.0 221 | float alpha; // Alpha, 0.0 (transparent) to 1.0 (opaque) 222 | } hc_color_t; 223 | 224 | typedef struct hc_point_s // Point/coordinate 225 | { 226 | float left; // Horizontal position 227 | float top; // Vertical position 228 | } hc_point_t; 229 | 230 | typedef struct hc_size_s // Point/coordinate 231 | { 232 | float width; // Width 233 | float height; // Height 234 | } hc_size_t; 235 | 236 | typedef struct hc_border_props_s // CSS border properties 237 | { 238 | hc_color_t color; // Border color 239 | hc_border_style_t style; // Border style 240 | float width; // Border width 241 | } hc_border_props_t; 242 | 243 | typedef struct hc_box_shadow_s // Box shadow values 244 | { 245 | float horizontal_offset; 246 | float vertical_offset; 247 | float blur_radius; 248 | float spread_distance; 249 | hc_color_t color; 250 | bool inset; 251 | } hc_box_shadow_t; 252 | 253 | // Higher-level types */ 254 | 255 | 256 | typedef enum // What to compute 257 | { 258 | HC_COMPUTE_BASE, // Base content 259 | HC_COMPUTE_BEFORE, // Content before element 260 | HC_COMPUTE_AFTER, // Content after element 261 | HC_COMPUTE_FIRST_LINE, // First line of block 262 | HC_COMPUTE_FIRST_LETTER // First letter of block 263 | } hc_compute_t; 264 | 265 | typedef struct hc_border_s // All CSS border properties 266 | { 267 | hc_border_props_t left; 268 | hc_border_props_t top; 269 | hc_border_props_t right; 270 | hc_border_props_t bottom; 271 | } hc_border_t; 272 | 273 | typedef struct hc_border_radius_s // CSS border-xxx-radius properties 274 | { 275 | hc_size_t bottom_left; // Bottom-left border radius 276 | hc_size_t bottom_right; // Bottom-right border radius 277 | hc_size_t top_left; // Top-left border radius 278 | hc_size_t top_right; // Top-right border radius 279 | } hc_border_radius_t; 280 | 281 | typedef struct hc_box_s // CSS box properties 282 | { 283 | hc_rect_t bounds; // Computed bounds 284 | hc_size_t size; // Computed size 285 | hc_rect_t clip; // Clip bounds 286 | hc_size_t max_size; 287 | hc_size_t min_size; 288 | hc_background_attachment_t 289 | background_attachment; 290 | hc_background_box_t background_clip; 291 | hc_color_t background_color; 292 | const char *background_image; 293 | hc_background_box_t background_origin; 294 | hc_point_t background_position; 295 | hc_background_repeat_t background_repeat; 296 | hc_size_t background_size; 297 | hc_border_t border; 298 | const char *border_image; 299 | bool border_image_fill; 300 | hc_rect_t border_image_outset; 301 | hc_border_image_repeat_t border_image_repeat[2]; 302 | hc_rect_t border_image_slice; 303 | hc_rect_t border_image_width; 304 | hc_border_radius_t border_radius; 305 | hc_size_t border_spacing; 306 | hc_box_shadow_t box_shadow; 307 | hc_break_t break_after; 308 | hc_break_t break_before; 309 | hc_break_t break_inside; 310 | hc_float_t float_value; 311 | const char *list_style_image; 312 | hc_list_style_position_t 313 | list_style_position; 314 | hc_list_style_type_t list_style_type; 315 | hc_rect_t margin; 316 | int orphans; 317 | hc_overflow_t overflow; 318 | hc_rect_t padding; 319 | int widows; 320 | int z_index; 321 | } hc_box_t; 322 | 323 | typedef struct hc_media_s // CSS media properties 324 | { 325 | const char *type; // "print", "screen", etc. 326 | int color_bits; // Color bits 327 | int monochrome_bits;// Grayscale bits 328 | hc_rect_t margin; // Margins 329 | hc_size_t size; // Dimensions 330 | } hc_media_t; 331 | 332 | typedef struct hc_table_s // CSS table properties 333 | { 334 | hc_border_collapse_t border_collapse; 335 | hc_caption_side_t caption_side; 336 | hc_empty_cells_t empty_cells; 337 | hc_table_layout_t table_layout; 338 | } hc_table_t; 339 | 340 | typedef struct hc_text_s // CSS text properties 341 | { 342 | hc_color_t color; 343 | hc_direction_t direction; 344 | hc_font_t *font; // Loaded font 345 | const char *font_family; 346 | float font_size; 347 | float font_size_adjust; 348 | hc_font_stretch_t font_stretch; 349 | hc_font_style_t font_style; 350 | hc_font_variant_t font_variant; 351 | hc_font_weight_t font_weight; 352 | float letter_spacing; 353 | float line_height; 354 | const char *quotes[4]; 355 | hc_text_align_t text_align; 356 | hc_text_decoration_t text_decoration; 357 | float text_indent; 358 | hc_text_transform_t text_transform; 359 | hc_unicode_bidi_t unicode_bidi; 360 | hc_white_space_t white_space; 361 | float word_spacing; 362 | } hc_text_t; 363 | 364 | typedef struct _hc_css_s hc_css_t; // CSS data 365 | 366 | 367 | // 368 | // Functions... 369 | // 370 | 371 | extern void hcCSSDelete(hc_css_t *css); 372 | extern hc_css_t *hcCSSNew(hc_pool_t *pool); 373 | extern bool hcCSSImport(hc_css_t *css, hc_file_t *file); 374 | extern bool hcCSSImportDefault(hc_css_t *css); 375 | extern void hcCSSSetErrorCallback(hc_css_t *css, hc_error_cb_t cb, void *ctx); 376 | extern void hcCSSSetURLCallback(hc_css_t *css, hc_url_cb_t cb, void *ctx); 377 | extern int hcCSSSetMedia(hc_css_t *css, const char *type, int color_bits, int grayscale_bits, float width, float height); 378 | 379 | 380 | # ifdef __cplusplus 381 | } 382 | # endif // __cplusplus 383 | #endif // !HTMLCSS_CSS_H 384 | -------------------------------------------------------------------------------- /default-css.h: -------------------------------------------------------------------------------- 1 | static const char *default_css = 2 | "html, address,\n" 3 | "blockquote,\n" 4 | "body, dd, div,\n" 5 | "dl, dt, fieldset, form,\n" 6 | "frame, frameset,\n" 7 | "h1, h2, h3, h4,\n" 8 | "h5, h6, noframes,\n" 9 | "ol, p, ul, center,\n" 10 | "dir, hr, menu, pre { display: block; unicode-bidi: embed; }\n" 11 | "li { display: list-item; }\n" 12 | "head { display: none; }\n" 13 | "table { display: table; }\n" 14 | "tr { display: table-row; }\n" 15 | "thead { display: table-header-group; }\n" 16 | "tbody { display: table-row-group; }\n" 17 | "tfoot { display: table-footer-group; }\n" 18 | "col { display: table-column; }\n" 19 | "colgroup { display: table-column-group; }\n" 20 | "td, th { display: table-cell; }\n" 21 | "caption { display: table-caption; }\n" 22 | "th { font-weight: bolder; text-align: center; }\n" 23 | "caption { text-align: center; }\n" 24 | "body { margin: 8px; }\n" 25 | "h1 { font-size: 2em; margin: .67em 0; }\n" 26 | "h2 { font-size: 1.5em; margin: .75em 0; }\n" 27 | "h3 { font-size: 1.17em; margin: .83em 0; }\n" 28 | "h4, p,\n" 29 | "blockquote, ul,\n" 30 | "fieldset, form,\n" 31 | "ol, dl, dir,\n" 32 | "menu { margin: 1.12em 0; }\n" 33 | "h5 { font-size: .83em; margin: 1.5em 0; }\n" 34 | "h6 { font-size: .75em; margin: 1.67em 0; }\n" 35 | "h1, h2, h3, h4,\n" 36 | "h5, h6, b,\n" 37 | "strong { font-weight: bolder; }\n" 38 | "blockquote { margin-left: 40px; margin-right: 40px; }\n" 39 | "i, cite, em,\n" 40 | "var, address { font-style: italic; }\n" 41 | "pre, tt, code,\n" 42 | "kbd, samp { font-family: monospace; }\n" 43 | "pre { white-space: pre; }\n" 44 | "button, textarea,\n" 45 | "input, select { display: inline-block; }\n" 46 | "big { font-size: 1.17em; }\n" 47 | "small, sub, sup { font-size: .83em; }\n" 48 | "sub { vertical-align: sub; }\n" 49 | "sup { vertical-align: super; }\n" 50 | "table { border-spacing: 2px; }\n" 51 | "thead, tbody,\n" 52 | "tfoot { vertical-align: middle; }\n" 53 | "td, th, tr { vertical-align: inherit; }\n" 54 | "s, strike, del { text-decoration: line-through; }\n" 55 | "hr { border: 1px inset; }\n" 56 | "ol, ul, dir,\n" 57 | "menu, dd { margin-left: 40px; }\n" 58 | "ol { list-style-type: decimal; }\n" 59 | "ol ul, ul ol,\n" 60 | "ul ul, ol ol { margin-top: 0; margin-bottom: 0; }\n" 61 | "u, ins { text-decoration: underline; }\n" 62 | "br:before { content: \"\\A\"; white-space: pre-line; }\n" 63 | "center { text-align: center; }\n" 64 | ":link, :visited { text-decoration: underline; }\n" 65 | ":focus { outline: thin dotted invert; }\n" 66 | "\n" 67 | "/* Begin bidirectionality settings (do not change) */\n" 68 | "/*BDO[DIR=\"ltr\"] { direction: ltr; unicode-bidi: bidi-override; }\n" 69 | "BDO[DIR=\"rtl\"] { direction: rtl; unicode-bidi: bidi-override; }*/\n" 70 | "\n" 71 | "*[DIR=\"ltr\"] { direction: ltr; unicode-bidi: embed; }\n" 72 | "*[DIR=\"rtl\"] { direction: rtl; unicode-bidi: embed; }\n" 73 | "\n" 74 | "@media print {\n" 75 | " h1 { page-break-before: always; }\n" 76 | " h1, h2, h3,\n" 77 | " h4, h5, h6 { page-break-after: avoid; }\n" 78 | " ul, ol, dl { page-break-before: avoid; }\n" 79 | "}\n" 80 | ; 81 | -------------------------------------------------------------------------------- /default.css: -------------------------------------------------------------------------------- 1 | html, address, 2 | blockquote, 3 | body, dd, div, 4 | dl, dt, fieldset, form, 5 | frame, frameset, 6 | h1, h2, h3, h4, 7 | h5, h6, noframes, 8 | ol, p, ul, center, 9 | dir, hr, menu, pre { display: block; unicode-bidi: embed; } 10 | li { display: list-item; } 11 | head { display: none; } 12 | table { display: table; } 13 | tr { display: table-row; } 14 | thead { display: table-header-group; } 15 | tbody { display: table-row-group; } 16 | tfoot { display: table-footer-group; } 17 | col { display: table-column; } 18 | colgroup { display: table-column-group; } 19 | td, th { display: table-cell; } 20 | caption { display: table-caption; } 21 | th { font-weight: bolder; text-align: center; } 22 | caption { text-align: center; } 23 | body { margin: 8px; } 24 | h1 { font-size: 2em; margin: .67em 0; } 25 | h2 { font-size: 1.5em; margin: .75em 0; } 26 | h3 { font-size: 1.17em; margin: .83em 0; } 27 | h4, p, 28 | blockquote, ul, 29 | fieldset, form, 30 | ol, dl, dir, 31 | menu { margin: 1.12em 0; } 32 | h5 { font-size: .83em; margin: 1.5em 0; } 33 | h6 { font-size: .75em; margin: 1.67em 0; } 34 | h1, h2, h3, h4, 35 | h5, h6, b, 36 | strong { font-weight: bolder; } 37 | blockquote { margin-left: 40px; margin-right: 40px; } 38 | i, cite, em, 39 | var, address { font-style: italic; } 40 | pre, tt, code, 41 | kbd, samp { font-family: monospace; } 42 | pre { white-space: pre; } 43 | button, textarea, 44 | input, select { display: inline-block; } 45 | big { font-size: 1.17em; } 46 | small, sub, sup { font-size: .83em; } 47 | sub { vertical-align: sub; } 48 | sup { vertical-align: super; } 49 | table { border-spacing: 2px; } 50 | thead, tbody, 51 | tfoot { vertical-align: middle; } 52 | td, th, tr { vertical-align: inherit; } 53 | s, strike, del { text-decoration: line-through; } 54 | hr { border: 1px inset; } 55 | ol, ul, dir, 56 | menu, dd { margin-left: 40px; } 57 | ol { list-style-type: decimal; } 58 | ol ul, ul ol, 59 | ul ul, ol ol { margin-top: 0; margin-bottom: 0; } 60 | u, ins { text-decoration: underline; } 61 | br:before { content: "\A"; white-space: pre-line; } 62 | center { text-align: center; } 63 | :link, :visited { text-decoration: underline; } 64 | :focus { outline: thin dotted invert; } 65 | 66 | /* Begin bidirectionality settings (do not change) */ 67 | /*BDO[DIR="ltr"] { direction: ltr; unicode-bidi: bidi-override; } 68 | BDO[DIR="rtl"] { direction: rtl; unicode-bidi: bidi-override; }*/ 69 | 70 | *[DIR="ltr"] { direction: ltr; unicode-bidi: embed; } 71 | *[DIR="rtl"] { direction: rtl; unicode-bidi: embed; } 72 | 73 | @media print { 74 | h1 { page-break-before: always; } 75 | h1, h2, h3, 76 | h4, h5, h6 { page-break-after: avoid; } 77 | ul, ol, dl { page-break-before: avoid; } 78 | } 79 | -------------------------------------------------------------------------------- /dict.c: -------------------------------------------------------------------------------- 1 | // 2 | // HTML attribute functions for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #include "common-private.h" 13 | #include "dict.h" 14 | 15 | 16 | // 17 | // Private types... 18 | // 19 | 20 | typedef struct _hc_pair_s // Key/value pair 21 | { 22 | const char *key; // Key 23 | const char *value; // Value 24 | } _hc_pair_t; 25 | 26 | struct _hc_dict_s // Dictionary 27 | { 28 | hc_pool_t *pool; // Memory pool 29 | size_t num_pairs; // Number of pairs 30 | size_t alloc_pairs; // Allocated pairs 31 | _hc_pair_t *pairs; // Key/value pairs 32 | }; 33 | 34 | 35 | // 36 | // Local functions... 37 | // 38 | 39 | static int compare_pairs(_hc_pair_t *a, _hc_pair_t *b); 40 | 41 | 42 | // 43 | // 'hcDictCopy()' - Make a copy of a dictionary. 44 | // 45 | 46 | hc_dict_t * // O - New dictionary 47 | hcDictCopy(const hc_dict_t *dict) // I - Dictionary to copy 48 | { 49 | hc_dict_t *newdict; // New dictionary 50 | 51 | 52 | if (!dict) 53 | return (NULL); 54 | 55 | if ((newdict = calloc(1, sizeof(hc_dict_t))) == NULL) 56 | return (NULL); 57 | 58 | newdict->pool = dict->pool; 59 | newdict->num_pairs = dict->num_pairs; 60 | newdict->alloc_pairs = dict->num_pairs; 61 | 62 | if ((newdict->pairs = calloc(newdict->num_pairs, sizeof(_hc_pair_t))) == NULL) 63 | { 64 | free(newdict); 65 | return (NULL); 66 | } 67 | 68 | memcpy(newdict->pairs, dict->pairs, newdict->num_pairs * sizeof(_hc_pair_t)); 69 | 70 | return (newdict); 71 | } 72 | 73 | 74 | // 75 | // 'hcDictDelete()' - Delete a dictionary. 76 | // 77 | 78 | void 79 | hcDictDelete(hc_dict_t *dict) // I - Dictionary 80 | { 81 | if (dict) 82 | { 83 | if (dict->pairs) 84 | free(dict->pairs); 85 | 86 | free(dict); 87 | } 88 | } 89 | 90 | 91 | // 92 | // 'hcDictGetCount()' - Return the number of key/value pairs in a dictionary. 93 | // 94 | 95 | size_t // O - Number of key/value pairs 96 | hcDictGetCount(const hc_dict_t *dict) // I - Dictionary 97 | { 98 | return (dict ? dict->num_pairs : 0); 99 | } 100 | 101 | 102 | // 103 | // 'hcDictGetIndexKeyValue()' - Return the key and value for the specified pair. 104 | // 105 | 106 | const char * // O - Value or `NULL` if `idx` is invalid. 107 | hcDictGetIndexKeyValue( 108 | const hc_dict_t *dict, // I - Dictionary 109 | size_t idx, // I - Index (0-based) 110 | const char **key) // O - Key or `NULL` if `idx` is invalid. 111 | { 112 | if (!dict || idx >= dict->num_pairs || !key) 113 | return (NULL); 114 | 115 | *key = dict->pairs[idx].key; 116 | 117 | return (dict->pairs[idx].value); 118 | } 119 | 120 | 121 | // 122 | // 'hcDictGetKeyValue()' - Get the value for a key in a dictionary. 123 | // 124 | 125 | const char * // O - Value or `NULL` if not found. 126 | hcDictGetKeyValue( 127 | const hc_dict_t *dict, // I - Dictionary 128 | const char *key) // I - Key string 129 | { 130 | _hc_pair_t temp, // Temporary search key 131 | *ptr; // Pointer to match 132 | 133 | 134 | if (!dict || dict->num_pairs == 0) 135 | return (NULL); 136 | 137 | temp.key = key; 138 | temp.value = NULL; 139 | 140 | if ((ptr = (_hc_pair_t *)bsearch(&temp, dict->pairs, dict->num_pairs, sizeof(_hc_pair_t), (_hc_compare_func_t)compare_pairs)) != NULL) 141 | return (ptr->value); 142 | else 143 | return (NULL); 144 | } 145 | 146 | 147 | // 148 | // 'hcDictNew()' - Create a new dictionary. 149 | // 150 | 151 | hc_dict_t * // O - New dictionary 152 | hcDictNew(hc_pool_t *pool) // I - Memory pool 153 | { 154 | hc_dict_t *dict; // New dictionary 155 | 156 | 157 | if ((dict = (hc_dict_t *)calloc(1, sizeof(hc_dict_t))) != NULL) 158 | dict->pool = pool; 159 | 160 | return (dict); 161 | } 162 | 163 | 164 | // 165 | // 'hcDictRemoveKey()' - Remove a key/value pair from a dictionary. 166 | // 167 | 168 | void 169 | hcDictRemoveKey(hc_dict_t *dict, // I - Dictionary 170 | const char *key) // I - Key string 171 | { 172 | _hc_pair_t temp, // Temporary search key 173 | *ptr; // Pointer to match 174 | size_t idx; // Index into dictionary 175 | 176 | 177 | if (!dict || dict->num_pairs == 0) 178 | return; 179 | 180 | temp.key = key; 181 | temp.value = NULL; 182 | 183 | if ((ptr = (_hc_pair_t *)bsearch(&temp, dict->pairs, dict->num_pairs, sizeof(_hc_pair_t), (_hc_compare_func_t)compare_pairs)) != NULL) 184 | { 185 | dict->num_pairs --; 186 | 187 | idx = (size_t)(ptr - dict->pairs); 188 | 189 | if (idx < dict->num_pairs) 190 | memmove(ptr, ptr + 1, (dict->num_pairs - idx) * sizeof(_hc_pair_t)); 191 | } 192 | } 193 | 194 | 195 | // 196 | // 'hcDictSetKeyValue()' - Set a key/value pair in a dictionary. 197 | // 198 | 199 | void 200 | hcDictSetKeyValue(hc_dict_t *dict, // I - Dictionary 201 | const char *key, // I - Key string 202 | const char *value) // I - Value string 203 | { 204 | _hc_pair_t temp, // Search key 205 | *ptr = NULL; // New key/value pair 206 | 207 | 208 | _HC_DEBUG("hcDictSetKeyValue(dict=%p, key=\"%s\", value=\"%s\")\n", (void *)dict, key, value); 209 | 210 | if (!dict) 211 | { 212 | return; 213 | } 214 | else if (dict->num_pairs == 1 && !strcmp(dict->pairs[0].key, key)) 215 | { 216 | ptr = dict->pairs; 217 | } 218 | else if (dict->num_pairs > 1) 219 | { 220 | temp.key = key; 221 | 222 | ptr = (_hc_pair_t *)bsearch(&temp, dict->pairs, dict->num_pairs, sizeof(_hc_pair_t), (_hc_compare_func_t)compare_pairs); 223 | } 224 | 225 | if (ptr) 226 | { 227 | ptr->value = hcPoolGetString(dict->pool, value); 228 | return; 229 | } 230 | 231 | if (dict->num_pairs >= dict->alloc_pairs) 232 | { 233 | if ((ptr = realloc(dict->pairs, (dict->alloc_pairs + 4) * sizeof(_hc_pair_t))) == NULL) 234 | return; 235 | 236 | dict->alloc_pairs += 4; 237 | dict->pairs = ptr; 238 | } 239 | 240 | ptr = dict->pairs + dict->num_pairs; 241 | dict->num_pairs ++; 242 | 243 | ptr->key = hcPoolGetString(dict->pool, key); 244 | ptr->value = hcPoolGetString(dict->pool, value); 245 | 246 | qsort(dict->pairs, dict->num_pairs, sizeof(_hc_pair_t), (_hc_compare_func_t)compare_pairs); 247 | 248 | #ifdef DEBUG 249 | size_t i; 250 | 251 | _HC_DEBUG("hxDictSetKeyValue: num_pairs=%d\n", (int)dict->num_pairs); 252 | for (i = 0, ptr = dict->pairs; i < dict->num_pairs; i ++, ptr ++) 253 | _HC_DEBUG("hcDictSetKeyValue: pairs[%d].key=\"%s\", .value=\"%s\"\n", (int)i, ptr->key, ptr->value); 254 | #endif // DEBUG 255 | } 256 | 257 | 258 | // 259 | // 'compare_pairs()' - Compare two key/value pairs. 260 | // 261 | 262 | static int // O - Result of comparison 263 | compare_pairs(_hc_pair_t *a, // I - First pair 264 | _hc_pair_t *b) // I - Second pair 265 | { 266 | #ifdef _WIN32 267 | return (_stricmp(a->key, b->key)); 268 | #else 269 | return (strcasecmp(a->key, b->key)); 270 | #endif // _WIN32 271 | } 272 | -------------------------------------------------------------------------------- /dict.h: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary header for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #ifndef HTMLCSS_DICT_H 13 | # define HTMLCSS_DICT_H 14 | # include "pool.h" 15 | # ifdef __cplusplus 16 | extern "C" { 17 | # endif // __cplusplus 18 | 19 | 20 | // 21 | // Types... 22 | // 23 | 24 | typedef struct _hc_dict_s hc_dict_t; 25 | // Key/value string dictionary 26 | 27 | 28 | // 29 | // Functions... 30 | // 31 | 32 | extern hc_dict_t *hcDictCopy(const hc_dict_t *dict); 33 | extern void hcDictDelete(hc_dict_t *dict); 34 | extern size_t hcDictGetCount(const hc_dict_t *dict); 35 | extern const char *hcDictGetIndexKeyValue(const hc_dict_t *dict, size_t idx, const char **key); 36 | extern const char *hcDictGetKeyValue(const hc_dict_t *dict, const char *key); 37 | extern hc_dict_t *hcDictNew(hc_pool_t *pool); 38 | extern void hcDictRemoveKey(hc_dict_t *dict, const char *key); 39 | extern void hcDictSetKeyValue(hc_dict_t *dict, const char *key, const char *value); 40 | 41 | 42 | # ifdef __cplusplus 43 | } 44 | # endif // __cplusplus 45 | #endif // !HTMLCSS_DICT_H 46 | -------------------------------------------------------------------------------- /file-private.h: -------------------------------------------------------------------------------- 1 | // 2 | // Private file handling header for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #ifndef HTMLCSS_FILE_PRIVATE_H 13 | # define HTMLCSS_FILE_PRIVATE_H 14 | # include "file.h" 15 | # include "pool-private.h" 16 | # include 17 | # ifdef __cplusplus 18 | extern "C" { 19 | # endif // __cplusplus 20 | 21 | 22 | // 23 | // Types... 24 | // 25 | 26 | struct _hc_file_s // High-level file/stream 27 | { 28 | hc_pool_t *pool; // Memory pool 29 | const char *url; // URL or filename 30 | gzFile fp; // File pointer 31 | const _hc_uchar_t *buffer, // String buffer 32 | *bufptr, // Pointer into buffer 33 | *bufend; // End of buffer 34 | int linenum; // Current line number 35 | }; 36 | 37 | 38 | // 39 | // Functions... 40 | // 41 | 42 | extern bool _hcFileError(hc_file_t *file, const char *message, ...) _HC_FORMAT_ARGS(2,3); 43 | 44 | 45 | # ifdef __cplusplus 46 | } 47 | # endif // __cplusplus 48 | #endif // !HTMLCSS_FILE_PRIVATE_H 49 | -------------------------------------------------------------------------------- /file.c: -------------------------------------------------------------------------------- 1 | // 2 | // File handling functions for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #include "file-private.h" 13 | 14 | 15 | // 16 | // 'hcFileDelete()' - Close a file and free any memory associated with it. 17 | // 18 | 19 | void 20 | hcFileDelete(hc_file_t *file) // I - File 21 | { 22 | if (!file) 23 | return; 24 | 25 | if (file->fp) 26 | gzclose(file->fp); 27 | 28 | free(file); 29 | } 30 | 31 | 32 | // 33 | // '_hcFileError()' - Report an error while reading the specified file. 34 | // 35 | 36 | bool // O - `true` to continue, `false` to stop 37 | _hcFileError(hc_file_t *file, // I - File 38 | const char *message, // I - Printf-style message 39 | ...) // I - Additional arguments as needed 40 | { 41 | bool ret; // Return value 42 | char saniurl[1024], // Sanitized URL 43 | *saniptr, // Pointer into sanitized URL 44 | temp[1024]; // Temporary message buffer 45 | const char *urlptr; // Pointer into URL 46 | va_list ap; // Pointer to arguments 47 | 48 | 49 | if (file->url) 50 | { 51 | // Sanitize the URL/filename... 52 | for (saniptr = saniurl, urlptr = file->url; *urlptr && saniptr < (saniurl + sizeof(saniurl) - 1); urlptr ++) 53 | { 54 | if (*urlptr < ' ' || *urlptr == '%') 55 | *saniptr++ = '_'; 56 | else 57 | *saniptr++ = *urlptr; 58 | } 59 | *saniptr = '\0'; 60 | 61 | // Create a new message format string with the correct prefix... 62 | if (file->linenum) 63 | snprintf(temp, sizeof(temp), "%s:%d: %s", saniurl, file->linenum, message); 64 | else if (file->url) 65 | snprintf(temp, sizeof(temp), "%s: %s", saniurl, message); 66 | message = temp; 67 | } 68 | else if (file->linenum) 69 | { 70 | // Create a new message format string with a line number prefix... 71 | snprintf(temp, sizeof(temp), "%d: %s", file->linenum, message); 72 | message = temp; 73 | } 74 | 75 | va_start(ap, message); 76 | ret = _hcPoolErrorv(file->pool, file->linenum, message, ap); 77 | va_end(ap); 78 | 79 | return (ret); 80 | } 81 | 82 | 83 | // 84 | // 'hcFileGetc()' - Get a character from a file. 85 | // 86 | 87 | int // O - Character or `EOF` 88 | hcFileGetc(hc_file_t *file) // I - File 89 | { 90 | int ch; // Current character 91 | 92 | 93 | if (file->bufptr) 94 | { 95 | if (file->bufptr < file->bufend) 96 | ch = (int)*(file->bufptr)++; 97 | else 98 | ch = EOF; 99 | } 100 | else 101 | ch = gzgetc(file->fp); 102 | 103 | if (ch == '\n') 104 | file->linenum ++; 105 | 106 | return (ch); 107 | } 108 | 109 | 110 | // 111 | // 'hcFileNewBuffer()' - Create a new file buffer stream. 112 | // 113 | 114 | hc_file_t * // O - File 115 | hcFileNewBuffer(hc_pool_t *pool, // I - Memory pool 116 | const void *buffer, // I - Buffer 117 | size_t bytes) // I - Size of buffer 118 | { 119 | hc_file_t *file; // File 120 | 121 | 122 | if ((file = calloc(1, sizeof(hc_file_t))) != NULL) 123 | { 124 | file->pool = pool; 125 | file->buffer = buffer; 126 | file->bufptr = file->buffer; 127 | file->bufend = file->buffer + bytes; 128 | file->linenum = 1; 129 | } 130 | 131 | return (file); 132 | } 133 | 134 | 135 | // 136 | // 'hcFileNewString()' - Create a new file string stream. 137 | // 138 | 139 | hc_file_t * // O - File 140 | hcFileNewString(hc_pool_t *pool, // I - Memory pool 141 | const char *s) // I - String 142 | { 143 | return (hcFileNewBuffer(pool, s, strlen(s))); 144 | } 145 | 146 | 147 | // 148 | // 'hcFileNewURL()' - Create a new file URL stream. 149 | // 150 | 151 | hc_file_t * // O - File 152 | hcFileNewURL(hc_pool_t *pool, // I - Memory pool 153 | const char *url, // I - URL or filename 154 | const char *baseurl) // I - Base URL or `NULL` 155 | { 156 | hc_file_t *file; // File 157 | const char *filename; // Local file 158 | 159 | 160 | _HC_DEBUG("hcFileNewURL(pool=%p, url=\"%s\", baseurl=\"%s\")\n", (void *)pool, url, baseurl); 161 | 162 | if ((filename = hcPoolGetURL(pool, url, baseurl)) == NULL) 163 | return (NULL); 164 | 165 | _HC_DEBUG("hcFileNewURL: filename=\"%s\"\n", filename); 166 | 167 | if ((file = calloc(1, sizeof(hc_file_t))) != NULL) 168 | { 169 | file->pool = pool; 170 | file->url = filename; 171 | file->fp = gzopen(filename, "rb"); 172 | file->linenum = 1; 173 | 174 | if (!file->fp) 175 | { 176 | perror(filename); 177 | free(file); 178 | file = NULL; 179 | } 180 | } 181 | 182 | return (file); 183 | } 184 | 185 | 186 | // 187 | // 'hcFileRead()' - Read bytes from a file. 188 | // 189 | 190 | size_t // O - Number of bytes read 191 | hcFileRead(hc_file_t *file, // I - File 192 | void *buffer, // I - Buffer 193 | size_t bytes) // I - Number of bytes to read 194 | { 195 | ssize_t rbytes; // Number of bytes read 196 | 197 | 198 | if (!file || !buffer || bytes == 0) 199 | return (0); 200 | 201 | if (file->bufptr) 202 | { 203 | if ((size_t)(file->bufend - file->bufptr) < bytes) 204 | bytes = (size_t)(file->bufend - file->bufptr); 205 | 206 | if (bytes > 0) 207 | { 208 | memcpy(buffer, file->bufptr, bytes); 209 | file->bufptr += bytes; 210 | } 211 | 212 | return (bytes); 213 | } 214 | else if ((rbytes = gzread(file->fp, buffer, (unsigned)bytes)) < 0) 215 | return (0); 216 | else 217 | return ((size_t)rbytes); 218 | } 219 | 220 | 221 | // 222 | // 'hcFileSeek()' - Randomly access data within a file. 223 | // 224 | 225 | size_t // O - New file offset or 0 on error 226 | hcFileSeek(hc_file_t *file, // I - File 227 | size_t offset) // I - Offset within file 228 | { 229 | ssize_t soffset; // Seek offset 230 | 231 | 232 | if (!file) 233 | return (0); 234 | 235 | if (file->bufptr) 236 | { 237 | if (offset > (size_t)(file->bufend - file->buffer)) 238 | offset = (size_t)(file->bufend - file->buffer); 239 | 240 | file->bufptr = file->buffer + offset; 241 | 242 | return (offset); 243 | } 244 | 245 | if ((soffset = gzseek(file->fp, (long)offset, SEEK_SET)) < 0) 246 | return (0); 247 | else 248 | return ((size_t)soffset); 249 | } 250 | 251 | 252 | // 253 | // 'hcFileUngetc()' - Return a character to a file. 254 | // 255 | 256 | void 257 | hcFileUngetc(hc_file_t *f, // I - File 258 | int ch) // I - Character 259 | { 260 | if (f->bufptr && f->bufptr > f->buffer) 261 | f->bufptr --; 262 | else if (f->fp) 263 | gzungetc(ch, f->fp); 264 | 265 | if (ch == '\n') 266 | f->linenum --; 267 | } 268 | -------------------------------------------------------------------------------- /file.h: -------------------------------------------------------------------------------- 1 | // 2 | // File handling header for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #ifndef HTMLCSS_FILE_H 13 | # define HTMLCSS_FILE_H 14 | # include "pool.h" 15 | # ifdef __cplusplus 16 | extern "C" { 17 | # endif // __cplusplus 18 | 19 | 20 | // 21 | // Types... 22 | // 23 | 24 | typedef struct _hc_file_s hc_file_t; // High-level file/stream 25 | 26 | 27 | // 28 | // Functions... 29 | // 30 | 31 | extern void hcFileDelete(hc_file_t *file); 32 | extern int hcFileGetc(hc_file_t *file); 33 | extern hc_file_t *hcFileNewBuffer(hc_pool_t *pool, const void *buffer, size_t bytes); 34 | extern hc_file_t *hcFileNewString(hc_pool_t *pool, const char *s); 35 | extern hc_file_t *hcFileNewURL(hc_pool_t *pool, const char *url, const char *baseurl); 36 | extern size_t hcFileRead(hc_file_t *file, void *buffer, size_t bytes); 37 | extern size_t hcFileSeek(hc_file_t *file, size_t offset); 38 | extern void hcFileUngetc(hc_file_t *file, int ch); 39 | 40 | 41 | # ifdef __cplusplus 42 | } 43 | # endif // __cplusplus 44 | #endif // !HTMLCSS_FILE_H 45 | -------------------------------------------------------------------------------- /font-extents.c: -------------------------------------------------------------------------------- 1 | // 2 | // Font extents function for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #include "font-private.h" 13 | 14 | 15 | // 16 | // 'hcFontComputeExtents()' - Compute the extents of a string when rendered using the 17 | // given font object, size, style, etc. 18 | // 19 | 20 | int // O - 1 on success, 0 on failure 21 | hcFontComputeExtents( 22 | const hc_font_t *font, // I - Font object 23 | float size, // I - Font size 24 | const char *s, // I - String 25 | hc_rect_t *extents) // O - Extents of the string 26 | { 27 | int ch, // Current character 28 | first = 1, // First character 29 | width = 0; // Width 30 | _hc_font_metric_t *widths; // Widths 31 | 32 | 33 | _HC_DEBUG("hcFontComputeExtents(font=%p, size=%.2f, s=\"%s\", extents=%p)\n", (void *)font, size, s, (void *)extents); 34 | 35 | if (extents) 36 | memset(extents, 0, sizeof(hc_rect_t)); 37 | 38 | if (!font || size <= 0.0f || !s || !extents) 39 | return (0); 40 | 41 | while (*s) 42 | { 43 | if ((*s & 0xe0) == 0xc0 && (s[1] & 0xc0) == 0x80) 44 | { 45 | ch = ((*s & 0x1f) << 6) | (s[1] & 0x3f); 46 | s += 2; 47 | } 48 | else if ((*s & 0xf0) == 0xe0 && (s[1] & 0xc0) == 0x80 && (s[2] & 0xc0) == 0x80) 49 | { 50 | ch = ((*s & 0x0f) << 12) | ((s[1] & 0x3f) << 6) | (s[2] & 0x3f); 51 | s += 3; 52 | } 53 | else if ((*s & 0xf8) == 0xf0 && (s[1] & 0xc0) == 0x80 && (s[2] & 0xc0) == 0x80 && (s[3] & 0xc0) == 0x80) 54 | { 55 | ch = ((*s & 0x07) << 18) | ((s[1] & 0x3f) << 12) | ((s[2] & 0x3f) << 6) | (s[3] & 0x3f); 56 | s += 4; 57 | } 58 | else if (*s & 0x80) 59 | return (0); 60 | else 61 | ch = *s++; 62 | 63 | if ((widths = font->widths[ch / 256]) != NULL) 64 | { 65 | if (first) 66 | { 67 | extents->left = -widths[ch & 255].left_bearing / font->units; 68 | first = 0; 69 | } 70 | 71 | width += widths[ch & 255].width; 72 | } 73 | } 74 | 75 | _HC_DEBUG("hcFontComputeExtents: width=%d\n", width); 76 | 77 | extents->bottom = size * font->y_min / font->units; 78 | extents->right = size * width / font->units + extents->left; 79 | extents->top = size * font->y_max / font->units; 80 | 81 | return (1); 82 | } 83 | -------------------------------------------------------------------------------- /font-private.h: -------------------------------------------------------------------------------- 1 | // 2 | // Private font header file for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #ifndef HTMLCSS_FONT_PRIVATE_H 13 | # define HTMLCSS_FONT_PRIVATE_H 14 | # include "common-private.h" 15 | # include "font.h" 16 | # ifdef __cplusplus 17 | extern "C" { 18 | # endif // __cplusplus 19 | 20 | 21 | // 22 | // Constants... 23 | // 24 | 25 | # define _HC_FONT_MAX_CHAR 262144 26 | # define _HC_FONT_MAX_GROUPS 65536 // Maximum number of sub-groups 27 | # define _HC_FONT_MAX_NAMES 16777216// Maximum size of names table we support 28 | 29 | 30 | // 31 | // Types... 32 | // 33 | 34 | typedef struct _hc_font_metric_s // Font metric information 35 | { 36 | short width, // Advance width 37 | left_bearing; // Left side bearing 38 | } _hc_font_metric_t; 39 | 40 | struct _hc_font_s 41 | { 42 | hc_pool_t *pool; // Memory pool 43 | size_t idx; // Font number in file 44 | size_t num_fonts; // Number of fonts in this file 45 | const char *copyright; // Copyright string 46 | const char *family; // Font family string 47 | const char *postscript_name; 48 | // PostScript name string 49 | const char *version; // Font version string 50 | bool is_fixed; // Is this a fixed-width font? 51 | int max_char, // Last character in font 52 | min_char; // First character in font 53 | size_t num_cmap; // Number of entries in glyph map 54 | int *cmap; // Unicode character to glyph map 55 | _hc_font_metric_t *widths[_HC_FONT_MAX_CHAR / 256]; 56 | // Character metrics (sparse array) 57 | float units; // Width units 58 | short ascent, // Maximum ascent above baseline 59 | descent, // Maximum descent below baseline 60 | cap_height, // "A" height 61 | x_height, // "x" height 62 | x_max, // Bounding box 63 | x_min, 64 | y_max, 65 | y_min, 66 | weight; // Font weight 67 | float italic_angle; // Angle of italic text 68 | hc_font_stretch_t stretch; // Font stretch value 69 | hc_font_style_t style; // Font style 70 | }; 71 | 72 | 73 | # ifdef __cplusplus 74 | } 75 | # endif // __cplusplus 76 | #endif // !HTMLCSS_FONT_PRIVATE_H 77 | -------------------------------------------------------------------------------- /font.h: -------------------------------------------------------------------------------- 1 | // 2 | // Font header file for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #ifndef HTMLCSS_FONT_H 13 | # define HTMLCSS_FONT_H 14 | # include "file.h" 15 | # ifdef __cplusplus 16 | extern "C" { 17 | # endif // __cplusplus 18 | 19 | 20 | // 21 | // Types... 22 | // 23 | 24 | typedef enum // Font stretch 25 | { 26 | HC_FONT_STRETCH_NORMAL, // normal 27 | HC_FONT_STRETCH_ULTRA_CONDENSED, // ultra-condensed 28 | HC_FONT_STRETCH_EXTRA_CONDENSED, // extra-condensed 29 | HC_FONT_STRETCH_CONDENSED, // condensed 30 | HC_FONT_STRETCH_SEMI_CONDENSED, // semi-condensed 31 | HC_FONT_STRETCH_SEMI_EXPANDED, // semi-expanded 32 | HC_FONT_STRETCH_EXPANDED, // expanded 33 | HC_FONT_STRETCH_EXTRA_EXPANDED, // extra-expanded 34 | HC_FONT_STRETCH_ULTRA_EXPANDED // ultra-expanded 35 | } hc_font_stretch_t; 36 | 37 | typedef enum // Font style 38 | { 39 | HC_FONT_STYLE_NORMAL, // Normal font 40 | HC_FONT_STYLE_ITALIC, // Italic font 41 | HC_FONT_STYLE_OBLIQUE // Oblique (angled) font 42 | } hc_font_style_t; 43 | 44 | typedef enum // Font variant 45 | { 46 | HC_FONT_VARIANT_NORMAL, // Normal font 47 | HC_FONT_VARIANT_SMALL_CAPS // Font whose lowercase letters are small capitals 48 | } hc_font_variant_t; 49 | 50 | typedef enum // Font weight 51 | { 52 | HC_FONT_WEIGHT_NORMAL, // Normal weight, nominally 400 53 | HC_FONT_WEIGHT_BOLD, // Bold weight, nominally 700 54 | HC_FONT_WEIGHT_BOLDER, // Bolder 55 | HC_FONT_WEIGHT_LIGHTER, // Lighter 56 | HC_FONT_WEIGHT_100 = 100, // Weight 100 (Thin) 57 | HC_FONT_WEIGHT_200 = 200, // Weight 200 (Extra/Ultra-Light) 58 | HC_FONT_WEIGHT_300 = 300, // Weight 300 (Light) 59 | HC_FONT_WEIGHT_400 = 400, // Weight 400 (Normal/Regular) 60 | HC_FONT_WEIGHT_500 = 500, // Weight 500 (Medium) 61 | HC_FONT_WEIGHT_600 = 600, // Weight 600 (Semi/Demi-Bold) 62 | HC_FONT_WEIGHT_700 = 700, // Weight 700 (Bold) 63 | HC_FONT_WEIGHT_800 = 800, // Weight 800 (Extra/Ultra-Bold) 64 | HC_FONT_WEIGHT_900 = 900 // Weight 900 (Black/Heavy) 65 | } hc_font_weight_t; 66 | 67 | typedef struct _hc_font_s hc_font_t; // Font object 68 | 69 | typedef struct hc_rect_s // Rectangle 70 | { 71 | float left; // Left offset 72 | float top; // Top offset 73 | float right; // Right offset 74 | float bottom; // Bottom offset 75 | } hc_rect_t; 76 | 77 | 78 | // 79 | // Functions... 80 | // 81 | 82 | extern void hcFontAddCached(hc_pool_t *pool, hc_font_t *font, const char *url); 83 | extern int hcFontComputeExtents(const hc_font_t *font, float size, const char *s, hc_rect_t *extents); 84 | extern void hcFontDelete(hc_font_t *font); 85 | extern hc_font_t *hcFontFindCached(hc_pool_t *pool, const char *family, hc_font_stretch_t stretch, hc_font_style_t style, hc_font_variant_t variant, hc_font_weight_t weight); 86 | extern int hcFontGetAscent(hc_font_t *font); 87 | extern hc_rect_t *hcFontGetBounds(hc_font_t *font, hc_rect_t *bounds); 88 | extern hc_font_t *hcFontGetCached(hc_pool_t *pool, size_t idx); 89 | extern size_t hcFontGetCachedCount(hc_pool_t *pool); 90 | extern int hcFontGetCapHeight(hc_font_t *font); 91 | extern const int *hcFontGetCMap(hc_font_t *font, size_t *num_cmap); 92 | extern const char *hcFontGetCopyright(hc_font_t *font); 93 | extern int hcFontGetDescent(hc_font_t *font); 94 | extern hc_rect_t *hcFontGetExtents(hc_font_t *font, float size, const char *s, hc_rect_t *extents); 95 | extern const char *hcFontGetFamily(hc_font_t *font); 96 | extern size_t hcFontGetNumFonts(hc_font_t *font); 97 | extern const char *hcFontGetPostScriptName(hc_font_t *font); 98 | extern hc_font_style_t hcFontGetStyle(hc_font_t *font); 99 | extern const char *hcFontGetVersion(hc_font_t *font); 100 | extern hc_font_weight_t hcFontGetWeight(hc_font_t *font); 101 | extern int hcFontGetWidth(hc_font_t *font, int ch); 102 | extern int hcFontGetXHeight(hc_font_t *font); 103 | extern bool hcFontIsFixedPitch(hc_font_t *font); 104 | extern hc_font_t *hcFontNew(hc_pool_t *pool, hc_file_t *file, size_t idx); 105 | 106 | 107 | # ifdef __cplusplus 108 | } 109 | # endif // __cplusplus 110 | #endif // !HTMLCSS_FONT_H 111 | -------------------------------------------------------------------------------- /html-attr.c: -------------------------------------------------------------------------------- 1 | // 2 | // HTML attribute functions for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #include "html-private.h" 13 | 14 | 15 | // 16 | // 'hcNodeAttrGetCount()' - Get the number of attributes for an element. 17 | // 18 | 19 | size_t // O - Number of attributes 20 | hcNodeAttrGetCount(hc_node_t *node) // I - Element node 21 | { 22 | if (!node || node->element < HC_ELEMENT_DOCTYPE) 23 | return (0); 24 | else 25 | return (hcDictGetCount(node->value.element.attrs)); 26 | } 27 | 28 | 29 | // 30 | // 'hcNodeAttrGetIndexNameValue()' - Get the name and value of a specified attribute. 31 | // 32 | 33 | const char * // O - Attribute value or `NULL` 34 | hcNodeAttrGetIndexNameValue( 35 | hc_node_t *node, // I - Element node 36 | size_t idx, // I - Attribute index (0-based) 37 | const char **name) // O - Attribute name 38 | { 39 | if (!node || node->element < HC_ELEMENT_DOCTYPE || !name) 40 | return (NULL); 41 | 42 | return (hcDictGetIndexKeyValue(node->value.element.attrs, idx, name)); 43 | } 44 | 45 | 46 | // 47 | // 'hcNodeAttrGetNameValue()' - Get the value of an element attribute. 48 | // 49 | 50 | const char * // O - Value or `NULL` if not present 51 | hcNodeAttrGetNameValue( 52 | hc_node_t *node, // I - Element node 53 | const char *name) // I - Attribute name 54 | { 55 | if (!node || node->element < HC_ELEMENT_DOCTYPE || !name) 56 | return (NULL); 57 | 58 | return (hcDictGetKeyValue(node->value.element.attrs, name)); 59 | } 60 | 61 | 62 | // 63 | // 'hcNodeAttrRemove()' - Delete an element attribute. 64 | // 65 | 66 | void 67 | hcNodeAttrRemove(hc_node_t *node, // I - Element node 68 | const char *name) // I - Attribute name 69 | { 70 | if (!node || node->element < HC_ELEMENT_DOCTYPE || !name) 71 | return; 72 | 73 | hcDictRemoveKey(node->value.element.attrs, name); 74 | } 75 | 76 | 77 | // 78 | // 'hcNodeAttrSetNameValue()' - Add an element attribute. 79 | // 80 | 81 | void 82 | hcNodeAttrSetNameValue( 83 | hc_node_t *node, // I - Element node 84 | const char *name, // I - Attribute name 85 | const char *value) // I - Attribute value 86 | { 87 | if (!node || node->element < HC_ELEMENT_DOCTYPE || !name || !value) 88 | return; 89 | 90 | if (!node->value.element.attrs) 91 | node->value.element.attrs = hcDictNew(node->value.element.html->pool); 92 | 93 | hcDictSetKeyValue(node->value.element.attrs, name, value); 94 | } 95 | -------------------------------------------------------------------------------- /html-core.c: -------------------------------------------------------------------------------- 1 | // 2 | // HTML core functions for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #include "html-private.h" 13 | #include 14 | 15 | 16 | // 17 | // HTML element strings... 18 | // 19 | 20 | static const char * const elements[HC_ELEMENT_MAX] = 21 | { 22 | "", // "*" 23 | "!--", 24 | "!DOCTYPE", 25 | "a", 26 | "abbr", 27 | "acronym", 28 | "address", 29 | "applet", 30 | "area", 31 | "article", 32 | "aside", 33 | "audio", 34 | "b", 35 | "base", 36 | "basefont", 37 | "bdi", 38 | "bdo", 39 | "big", 40 | "blink", 41 | "blockquote", 42 | "body", 43 | "br", 44 | "button", 45 | "canvas", 46 | "caption", 47 | "center", 48 | "cite", 49 | "code", 50 | "col", 51 | "colgroup", 52 | "data", 53 | "datalist", 54 | "dd", 55 | "del", 56 | "details", 57 | "dfn", 58 | "dialog", 59 | "dir", 60 | "div", 61 | "dl", 62 | "dt", 63 | "em", 64 | "embed", 65 | "fieldset", 66 | "figcaption", 67 | "figure", 68 | "font", 69 | "footer", 70 | "form", 71 | "frame", 72 | "frameset", 73 | "h1", 74 | "h2", 75 | "h3", 76 | "h4", 77 | "h5", 78 | "h6", 79 | "head", 80 | "header", 81 | "hr", 82 | "html", 83 | "i", 84 | "iframe", 85 | "img", 86 | "input", 87 | "ins", 88 | "isindex", 89 | "kbd", 90 | "label", 91 | "legend", 92 | "li", 93 | "link", 94 | "main", 95 | "map", 96 | "mark", 97 | "menu", 98 | "meta", 99 | "meter", 100 | "multicol", 101 | "nav", 102 | "nobr", 103 | "noframes", 104 | "noscript", 105 | "object", 106 | "ol", 107 | "optgroup", 108 | "option", 109 | "output", 110 | "p", 111 | "param", 112 | "picture", 113 | "pre", 114 | "progress", 115 | "q", 116 | "rb", 117 | "rp", 118 | "rt", 119 | "rtc", 120 | "ruby", 121 | "s", 122 | "samp", 123 | "script", 124 | "section", 125 | "select", 126 | "small", 127 | "source", 128 | "spacer", 129 | "span", 130 | "strike", 131 | "strong", 132 | "style", 133 | "sub", 134 | "summary", 135 | "sup", 136 | "table", 137 | "tbody", 138 | "td", 139 | "template", 140 | "textarea", 141 | "tfoot", 142 | "th", 143 | "thead", 144 | "time", 145 | "title", 146 | "tr", 147 | "track", 148 | "tt", 149 | "u", 150 | "ul", 151 | "var", 152 | "video", 153 | "wbr" 154 | }; 155 | 156 | 157 | // 158 | // Local functions... 159 | // 160 | 161 | static int compare_elements(const char **a, const char **b); 162 | 163 | 164 | // 165 | // 'hcElementString()' - Return the string associated with an element enum value. 166 | // 167 | 168 | const char * // O - HTML element string (lowercase) 169 | hcElementString(hc_element_t e) // I - HTML element enum 170 | { 171 | // Range check and return the appropriate string... 172 | if (e >= HC_ELEMENT_WILDCARD && e < HC_ELEMENT_MAX) 173 | return (elements[e]); 174 | else 175 | return ("(unknown)"); 176 | 177 | } 178 | 179 | 180 | // 181 | // 'hcElementValue()' - Return the enum associated with an element string value. 182 | // 183 | 184 | hc_element_t // O - HTML element enum 185 | hcElementValue(const char *s) // I - HTML element string 186 | { 187 | const char **match; // Match from bsearch 188 | 189 | 190 | // Range check input... 191 | if (!s || !*s) 192 | return (HC_ELEMENT_UNKNOWN); 193 | 194 | // Search for the string... 195 | if ((match = (const char **)bsearch(&s, elements, sizeof(elements) / sizeof(elements[0]), sizeof(elements[0]), (int (*)(const void *, const void *))compare_elements)) != NULL) 196 | return ((hc_element_t)(match - elements)); 197 | else 198 | return (HC_ELEMENT_UNKNOWN); 199 | } 200 | 201 | 202 | // 203 | // 'hcHTMLDelete()' - Free the memory used by a HTML document. 204 | // 205 | 206 | void 207 | hcHTMLDelete(hc_html_t *html) // I - HTML document 208 | { 209 | if (html) 210 | { 211 | hcNodeDelete(html, html->root); 212 | free(html); 213 | } 214 | } 215 | 216 | 217 | // 218 | // 'hcHTMLGetCSS()' - Get the stylesheet for a HTML document. 219 | // 220 | 221 | hc_css_t * // O - Stylesheet 222 | hcHTMLGetCSS(hc_html_t *html) // I - HTML document 223 | { 224 | return (html ? html->css : NULL); 225 | } 226 | 227 | 228 | // 229 | // 'hcNodeNew()' - Create a new HTML document. 230 | // 231 | 232 | hc_html_t * // O - HTML document 233 | hcHTMLNew(hc_pool_t *pool, // I - Memory pool 234 | hc_css_t *css) // I - Base stylesheet 235 | { 236 | hc_html_t *html; // New HTML document 237 | 238 | 239 | if ((html = (hc_html_t *)calloc(1, sizeof(hc_html_t))) != NULL) 240 | { 241 | html->pool = pool; 242 | html->css = css; 243 | html->error_cb = _hcDefaultErrorCB; 244 | html->url_cb = _hcDefaultURLCB; 245 | } 246 | 247 | return (html); 248 | } 249 | 250 | 251 | // 252 | // 'compare_elements()' - Compare two elements... 253 | // 254 | 255 | static int // O - Result of comparison 256 | compare_elements(const char **a, // I - First string 257 | const char **b) // I - Second string 258 | { 259 | #ifdef WIN32 260 | return (_stricmp(*a, *b)); 261 | #else 262 | return (strcasecmp(*a, *b)); 263 | #endif // WIN32 264 | } 265 | -------------------------------------------------------------------------------- /html-find.c: -------------------------------------------------------------------------------- 1 | // 2 | // HTML find functions for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #include "html-private.h" 13 | 14 | 15 | // 16 | // Local functions... 17 | // 18 | 19 | static hc_node_t *html_walk_next(hc_node_t *current); 20 | 21 | 22 | // 23 | // 'hcHTMLFindNode()' - Find the first node in a document that matches the given 24 | // element and/or ID string. 25 | // 26 | 27 | hc_node_t * // O - First matching node or `NULL` 28 | hcHTMLFindNode(hc_html_t *html, // I - HTML document 29 | hc_node_t *current, // I - Current node or `NULL` 30 | hc_element_t element, // I - Element or `HC_ELEMENT_WILDCARD` for any 31 | const char *id) // I - ID string or `NULL` for any 32 | { 33 | const char *current_id; // Current ID attribute 34 | 35 | 36 | if (!html) 37 | return (NULL); 38 | 39 | if (!current) 40 | current = html->root; 41 | else 42 | current = html_walk_next(current); 43 | 44 | while (current) 45 | { 46 | if (element == HC_ELEMENT_WILDCARD || current->element == element) 47 | { 48 | if (!id) 49 | break; 50 | 51 | if ((current_id = hcNodeAttrGetNameValue(current, "id")) != NULL && !strcmp(current_id, id)) 52 | break; 53 | } 54 | 55 | current = html_walk_next(current); 56 | } 57 | 58 | return (current); 59 | } 60 | 61 | 62 | // 63 | // 'html_walk_next()' - Walk the node tree. 64 | // 65 | 66 | static hc_node_t * // O - Next logical node or `NULL` 67 | html_walk_next(hc_node_t *current) // I - Current node 68 | { 69 | hc_node_t *next; // Next node 70 | 71 | 72 | if ((next = hcNodeGetFirstChildNode(current)) == NULL) 73 | { 74 | if ((next = hcNodeGetNextSiblingNode(current)) == NULL) 75 | { 76 | do 77 | { 78 | next = hcNodeGetParentNode(current); 79 | } 80 | while (next && hcNodeGetNextSiblingNode(next) == NULL); 81 | 82 | next = hcNodeGetNextSiblingNode(next); 83 | } 84 | } 85 | 86 | return (next); 87 | } 88 | -------------------------------------------------------------------------------- /html-load.c: -------------------------------------------------------------------------------- 1 | // 2 | // HTML load functions for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #include "html-private.h" 13 | #include "file-private.h" 14 | #include 15 | 16 | 17 | // 18 | // Local macros... 19 | // 20 | 21 | #define html_isblock(x) ((x) == HC_ELEMENT_ADDRESS || (x) == HC_ELEMENT_P || (x) == HC_ELEMENT_PRE || ((x) >= HC_ELEMENT_H1 && (x) <= HC_ELEMENT_H6) || (x) == HC_ELEMENT_HR || (x) == HC_ELEMENT_TABLE) 22 | #define html_isleaf(x) ((x) == HC_ELEMENT_AREA || (x) == HC_ELEMENT_BASE || (x) == HC_ELEMENT_BR || (x) == HC_ELEMENT_COL || (x) == HC_ELEMENT_EMBED || (x) == HC_ELEMENT_HR ||(x) == HC_ELEMENT_IMG || (x) == HC_ELEMENT_INPUT || (x) == HC_ELEMENT_ISINDEX || (x) == HC_ELEMENT_LINK || (x) == HC_ELEMENT_META || (x) == HC_ELEMENT_PARAM || (x) == HC_ELEMENT_SOURCE || (x) == HC_ELEMENT_SPACER || (x) == HC_ELEMENT_TRACK || (x) == HC_ELEMENT_WBR) 23 | #define html_islist(x) ((x) == HC_ELEMENT_DL || (x) == HC_ELEMENT_OL || (x) == HC_ELEMENT_UL || (x) == HC_ELEMENT_DIR || (x) == HC_ELEMENT_MENU) 24 | #define html_islentry(x) ((x) == HC_ELEMENT_LI || (x) == HC_ELEMENT_DD || (x) == HC_ELEMENT_DT) 25 | #define html_issuper(x) ((x) == HC_ELEMENT_CENTER || (x) == HC_ELEMENT_DIV || (x) == HC_ELEMENT_BLOCKQUOTE) 26 | #define html_istable(x) ((x) == HC_ELEMENT_TBODY || (x) == HC_ELEMENT_THEAD || (x) == HC_ELEMENT_TFOOT || (x) == HC_ELEMENT_TR) 27 | #define html_istentry(x) ((x) == HC_ELEMENT_TD || (x) == HC_ELEMENT_TH) 28 | 29 | 30 | // 31 | // Local functions... 32 | // 33 | 34 | static int html_parse_attr(hc_file_t *file, int ch, hc_node_t *node); 35 | static bool html_parse_comment(hc_file_t *file, hc_node_t **parent); 36 | static bool html_parse_doctype(hc_file_t *file, hc_html_t *html, hc_node_t **parent); 37 | static bool html_parse_element(hc_file_t *file, int ch, hc_html_t *html, hc_node_t **parent); 38 | static bool html_parse_unknown(hc_file_t *file, hc_node_t **parent, const char *unk); 39 | 40 | 41 | // 42 | // 'hcHTMLImport()' - Load a HTML file into a document. 43 | // 44 | 45 | bool // O - `true` on success, `false` on error 46 | hcHTMLImport(hc_html_t *html, // I - HTML document 47 | hc_file_t *file) // I - File to import 48 | { 49 | hc_node_t *parent = NULL; // Parent node 50 | bool status = true; // Load status 51 | int ch; // Current character 52 | char buffer[8192], // Temporary buffer 53 | *bufptr, // Pointer into buffer 54 | *bufend; // End of buffer 55 | 56 | 57 | // Range check input... 58 | if (!html || html->root || !file) 59 | return (false); 60 | 61 | // f.parent = NULL; 62 | 63 | // Parse file... 64 | bufptr = buffer; 65 | bufend = buffer + sizeof(buffer) - 1; 66 | 67 | while ((ch = hcFileGetc(file)) != EOF) 68 | { 69 | if (ch == '<') 70 | { 71 | // Read a HTML element... 72 | ch = hcFileGetc(file); 73 | 74 | if (isspace(ch) || ch == '=' || ch == '<') 75 | { 76 | // Sigh... "<" followed by anything but an element name is invalid 77 | // HTML, but many pages are still broken. Log it and abort if the error 78 | // callback says to... 79 | if (!_hcFileError(file, "Unquoted '<'.")) 80 | { 81 | status = false; 82 | break; 83 | } 84 | 85 | if (bufptr >= (bufend - 1)) 86 | { 87 | // Add text string... 88 | if (parent) 89 | { 90 | *bufptr = '\0'; 91 | hcNodeNewString(parent, buffer); 92 | bufptr = buffer; 93 | } 94 | else 95 | { 96 | status = false; 97 | _hcFileError(file, "Text without leading element or directive."); 98 | break; 99 | } 100 | } 101 | 102 | *bufptr++ = '<'; 103 | 104 | if (ch == '<') 105 | hcFileUngetc(file, ch); 106 | else 107 | *bufptr++ = (char)ch; 108 | } 109 | else 110 | { 111 | // Got the first character of an element name, add any pending text and 112 | // then parse the element... 113 | if (bufptr > buffer) 114 | { 115 | // Add text string... 116 | if (parent) 117 | { 118 | *bufptr = '\0'; 119 | hcNodeNewString(parent, buffer); 120 | bufptr = buffer; 121 | } 122 | else 123 | { 124 | status = false; 125 | _hcFileError(file, "Text without leading element or directive."); 126 | break; 127 | } 128 | } 129 | 130 | if (!(status = html_parse_element(file, ch, html, &parent))) 131 | break; 132 | } 133 | } 134 | else 135 | { 136 | if (bufptr < bufend) 137 | *bufptr++ = (char)ch; 138 | 139 | if (ch == '\n' || bufptr >= bufend) 140 | { 141 | if (parent) 142 | { 143 | *bufptr = '\0'; 144 | hcNodeNewString(parent, buffer); 145 | bufptr = buffer; 146 | } 147 | else 148 | { 149 | status = false; 150 | _hcFileError(file, "Text without leading element or directive."); 151 | } 152 | } 153 | } 154 | 155 | } 156 | 157 | if (bufptr > buffer) 158 | { 159 | // Add trailing text string... 160 | if (parent) 161 | { 162 | *bufptr = '\0'; 163 | hcNodeNewString(parent, buffer); 164 | } 165 | else 166 | { 167 | status = false; 168 | _hcFileError(file, "Text without leading element or directive."); 169 | } 170 | } 171 | 172 | return (status); 173 | } 174 | 175 | 176 | // 177 | // 'html_parse_attr()' - Parse an attribute. 178 | // 179 | 180 | static int // O - Character or `EOF` to stop 181 | html_parse_attr(hc_file_t *file, // I - File to read from 182 | int ch, // I - Initial character 183 | hc_node_t *node) // I - HTML element node 184 | { 185 | char name[256], // Name string 186 | value[2048], // Value string 187 | *ptr, // Pointer into string 188 | *end; // End of string 189 | 190 | 191 | // Read name... 192 | ptr = name; 193 | end = name + sizeof(name) - 1; 194 | 195 | do 196 | { 197 | if (ptr < end) 198 | *ptr++ = (char)tolower(ch); 199 | else 200 | break; 201 | } 202 | while ((ch = hcFileGetc(file)) != EOF && ch != '=' && ch != '>' && !isspace(ch)); 203 | 204 | *ptr = '\0'; 205 | 206 | if (ch == '=') 207 | { 208 | // Read value... 209 | ptr = value; 210 | end = value + sizeof(value) - 1; 211 | 212 | if ((ch = hcFileGetc(file)) == '\'' || ch == '\"') 213 | { 214 | int quote = ch; // Quote character 215 | 216 | while ((ch = hcFileGetc(file)) != EOF && ch != quote) 217 | { 218 | if (ptr < end) 219 | *ptr++ = (char)ch; 220 | else 221 | break; 222 | } 223 | } 224 | else if (!isspace(ch) && ch != '>' && ch != EOF) 225 | { 226 | do 227 | { 228 | if (ptr < end) 229 | *ptr++ = (char)ch; 230 | else 231 | break; 232 | } 233 | while ((ch = hcFileGetc(file)) != EOF && ch != '>' && !isspace(ch)); 234 | } 235 | 236 | *ptr = '\0'; 237 | hcNodeAttrSetNameValue(node, name, value); 238 | } 239 | else if (ch != EOF) 240 | { 241 | // Add "name=name"... 242 | hcNodeAttrSetNameValue(node, name, name); 243 | } 244 | 245 | return (ch); 246 | } 247 | 248 | 249 | // 250 | // 'html_parse_comment()' - Parse a comment. 251 | // 252 | 253 | static bool // O - `true` to continue, `false` to stop 254 | html_parse_comment(hc_file_t *file, // I - File to read from 255 | hc_node_t **parent) // IO - Parent node 256 | { 257 | int ch; // Current character 258 | char buffer[8192], // String buffer 259 | *bufptr, // Pointer into buffer 260 | *bufend; // End of buffer 261 | 262 | 263 | bufptr = buffer; 264 | bufend = buffer + sizeof(buffer) - 1; 265 | 266 | while ((ch = hcFileGetc(file)) != EOF) 267 | { 268 | if (ch == '>' && bufptr > (buffer + 1) && bufptr[-1] == '-' && bufptr[-2] == '-') 269 | { 270 | // End of comment... 271 | bufptr -= 2; 272 | break; 273 | } 274 | else if (bufptr < bufend) 275 | *bufptr++ = (char)ch; 276 | else if (!_hcFileError(file, "Comment too long.")) 277 | return (false); 278 | else 279 | break; 280 | } 281 | 282 | *bufptr = '\0'; 283 | 284 | hcNodeNewComment(*parent, buffer); 285 | 286 | if (ch == EOF) 287 | return (_hcFileError(file, "Unexpected end-of-file.")); 288 | else if (ch != '>') 289 | return (_hcFileError(file, "Comment too long.")); 290 | else 291 | return (true); 292 | } 293 | 294 | 295 | // 296 | // 'html_parse_doctype()' - Parse a DOCTYPE element. 297 | // 298 | 299 | static bool // O - `true` to continue, `false` to stop 300 | html_parse_doctype(hc_file_t *file, // I - File to read from 301 | hc_html_t *html, // I - HTML document 302 | hc_node_t **parent) // IO - Parent node 303 | { 304 | int ch; // Character from file 305 | char buffer[2048], // String buffer 306 | *bufptr, // Pointer into buffer 307 | *bufend; // End of buffer 308 | 309 | 310 | bufptr = buffer; 311 | bufend = buffer + sizeof(buffer) - 1; 312 | 313 | while ((ch = hcFileGetc(file)) != EOF) 314 | { 315 | if (!isspace(ch)) 316 | break; 317 | } 318 | 319 | while (ch != EOF && ch != '>') 320 | { 321 | if (bufptr < bufend) 322 | *bufptr++ = (char)ch; 323 | else 324 | break; 325 | 326 | if (ch == '\'' || ch == '\"') 327 | { 328 | int quote = ch; // Quote character 329 | 330 | while ((ch = hcFileGetc(file)) != EOF && ch != quote) 331 | { 332 | if (bufptr < bufend) 333 | *bufptr++ = (char)ch; 334 | else 335 | break; 336 | } 337 | } 338 | else 339 | ch = hcFileGetc(file); 340 | } 341 | 342 | *bufptr = '\0'; 343 | 344 | if (ch == EOF) 345 | { 346 | _hcFileError(file, "Unexpected end-of-file."); 347 | return (false); 348 | } 349 | else if (ch != '>') 350 | _hcFileError(file, " too long."); 351 | 352 | *parent = hcHTMLNewRootNode(html, buffer); 353 | 354 | return (*parent != NULL); 355 | } 356 | 357 | 358 | // 359 | // 'html_parse_element()' - Parse an element. 360 | // 361 | 362 | static bool // O - `true` to continue, `false` to stop 363 | html_parse_element(hc_file_t *file, // I - File to read from 364 | int ch, // I - Initial character after '<' 365 | hc_html_t *html, // I - HTML document 366 | hc_node_t **parent) // IO - Parent node 367 | { 368 | char buffer[256], // String buffer 369 | *bufptr, // Pointer into buffer 370 | *bufend; // End of buffer 371 | hc_element_t element; // Element index 372 | hc_node_t *node; // New node 373 | bool close_el = ch == '/'; // Close element? 374 | 375 | 376 | // Read the element name... 377 | bufptr = buffer; 378 | bufend = buffer + sizeof(buffer) - 1; 379 | if (!close_el) 380 | *bufptr++ = (char)ch; 381 | 382 | while ((ch = hcFileGetc(file)) != EOF) 383 | { 384 | if (isspace(ch) || ch == '>' || ch == '/') 385 | break; 386 | else if (bufptr < bufend) 387 | *bufptr++ = (char)ch; 388 | else if (!_hcFileError(file, "Element name too long.")) 389 | return (false); 390 | else 391 | break; 392 | 393 | if ((bufptr - buffer) == 3 && !memcmp(buffer, "!--", 3)) 394 | { 395 | // Comment without whitespace, pretend we got some... 396 | ch = ' '; 397 | break; 398 | } 399 | } 400 | 401 | if (ch == EOF) 402 | { 403 | _hcFileError(file, "Unexpected end-of-file."); 404 | return (false); 405 | } 406 | 407 | *bufptr = '\0'; 408 | 409 | // Convert the name to an enum... 410 | if (isspace(ch) || ch == '>' || ch == '/') 411 | { 412 | element = hcElementValue(buffer); 413 | 414 | if (element == HC_ELEMENT_UNKNOWN && !_hcFileError(file, "Unknown element '%s'.", buffer)) 415 | return (false); 416 | } 417 | else 418 | { 419 | hcFileUngetc(file, ch); 420 | element = HC_ELEMENT_UNKNOWN; 421 | } 422 | 423 | // Parse unknown, comment, and doctype elements accordingly... 424 | if (element == HC_ELEMENT_DOCTYPE) 425 | { 426 | if (close_el) 427 | { 428 | _hcFileError(file, "Invalid seem."); 429 | return (false); 430 | } 431 | else if (html->root) 432 | { 433 | _hcFileError(file, "Duplicate seen."); 434 | return (false); 435 | } 436 | 437 | return (html_parse_doctype(file, html, parent)); 438 | } 439 | else if (!*parent) 440 | { 441 | if (!_hcFileError(file, "Missing directive.")) 442 | return (false); 443 | 444 | *parent = hcHTMLNewRootNode(html, "html"); 445 | } 446 | else if (element == HC_ELEMENT_UNKNOWN) 447 | { 448 | if (close_el) 449 | { 450 | char unk[257]; // Unknown value 451 | 452 | snprintf(unk, sizeof(unk), "/%s", buffer); 453 | return (html_parse_unknown(file, parent, unk)); 454 | } 455 | else 456 | { 457 | return (html_parse_unknown(file, parent, buffer)); 458 | } 459 | } 460 | else if (element == HC_ELEMENT_COMMENT) 461 | { 462 | return (html_parse_comment(file, parent)); 463 | } 464 | 465 | // Otherwise add the element (or close it) in the right place... 466 | if (close_el) 467 | { 468 | // Close the specified element... 469 | if (ch != '>' && !_hcFileError(file, "Invalid element.", buffer)) 470 | return (false); 471 | 472 | for (node = *parent; node; node = node->parent) 473 | { 474 | if (node->element == element) 475 | break; 476 | } 477 | 478 | if (node) 479 | *parent = node->parent; 480 | else if (!_hcFileError(file, "Missing <%s> for element.", buffer, buffer)) 481 | return (false); 482 | 483 | return (true); 484 | } 485 | 486 | // HTML doesn't enforce strict open/close markup semantics, so allow

, 487 | //

  • , etc. to close out like markup... 488 | if (html_issuper(element)) 489 | { 490 | for (node = *parent; node; node = node->parent) 491 | { 492 | if (html_istentry(node->element)) 493 | break; 494 | } 495 | } 496 | else if (html_islist(element)) 497 | { 498 | for (node = *parent; node; node = node->parent) 499 | { 500 | if (html_isblock(node->element) || html_islentry(node->element) || html_istentry(node->element) || html_issuper(node->element)) 501 | break; 502 | } 503 | } 504 | else if (html_islentry(element)) 505 | { 506 | for (node = *parent; node; node = node->parent) 507 | { 508 | if (html_islist(node->element)) 509 | break; 510 | } 511 | } 512 | else if (html_isblock(element)) 513 | { 514 | for (node = *parent; node; node = node->parent) 515 | { 516 | if (html_isblock(node->element)) 517 | { 518 | node = node->parent; 519 | break; 520 | } 521 | else if (html_istentry(node->element) || html_islist(node->element) || html_islentry(node->element) || html_issuper(node->element)) 522 | { 523 | break; 524 | } 525 | } 526 | } 527 | else if (element == HC_ELEMENT_THEAD || element == HC_ELEMENT_TBODY || element == HC_ELEMENT_TFOOT) 528 | { 529 | for (node = *parent; node; node = node->parent) 530 | { 531 | if (node->element == HC_ELEMENT_TABLE) 532 | break; 533 | } 534 | } 535 | else if (html_istentry(element)) 536 | { 537 | for (node = *parent; node; node = node->parent) 538 | { 539 | if (html_istentry(node->element)) 540 | { 541 | node = node->parent; 542 | break; 543 | } 544 | else if (node->element == HC_ELEMENT_TR) 545 | { 546 | break; 547 | } 548 | else if (node->element == HC_ELEMENT_TABLE || html_istable(node->element)) 549 | { 550 | if (!_hcFileError(file, "No element before <%s> element.", buffer)) 551 | return (false); 552 | 553 | node = hcNodeNewElement(*parent, HC_ELEMENT_TR); 554 | break; 555 | } 556 | } 557 | } 558 | else 559 | { 560 | // No new parent... 561 | node = NULL; 562 | } 563 | 564 | if (node) 565 | *parent = node; 566 | 567 | node = hcNodeNewElement(*parent, element); 568 | 569 | if (ch != '/' && !html_isleaf(element)) 570 | *parent = node; 571 | 572 | while (ch != '>' && ch != EOF) 573 | { 574 | while ((ch = hcFileGetc(file)) != EOF) 575 | { 576 | if (!isspace(ch)) 577 | break; 578 | } 579 | 580 | if (ch != '>') 581 | ch = html_parse_attr(file, ch, node); 582 | } 583 | 584 | return (ch == '>'); 585 | } 586 | 587 | 588 | // 589 | // 'html_parse_unknown()' - Parse an unknown element or processing directive. 590 | // 591 | 592 | static bool // O - `true` to continue, `false` to stop 593 | html_parse_unknown(hc_file_t *file, // I - File to read from 594 | hc_node_t **parent, // IO - Parent node 595 | const char *unk) // I - Start of unknown markup 596 | { 597 | int ch; // Character from file 598 | char buffer[2048], // String buffer 599 | *bufptr, // Pointer into buffer 600 | *bufend; // End of buffer 601 | 602 | 603 | strncpy(buffer, unk, sizeof(buffer) - 1); 604 | buffer[sizeof(buffer) - 1] = '\0'; 605 | 606 | bufptr = buffer + strlen(buffer); 607 | bufend = buffer + sizeof(buffer) - 1; 608 | 609 | while ((ch = hcFileGetc(file)) != EOF && ch != '>') 610 | { 611 | if (bufptr < bufend) 612 | *bufptr++ = (char)ch; 613 | else 614 | break; 615 | 616 | if (ch == '\'' || ch == '\"') 617 | { 618 | int quote = ch; // Quote character 619 | 620 | while ((ch = hcFileGetc(file)) != EOF && ch != quote) 621 | { 622 | if (bufptr < bufend) 623 | *bufptr++ = (char)ch; 624 | else 625 | break; 626 | } 627 | 628 | if (ch == EOF || ch != quote || bufptr >= bufend) 629 | break; 630 | 631 | *bufptr++ = (char)ch; 632 | } 633 | } 634 | 635 | *bufptr = '\0'; 636 | 637 | if (ch == EOF) 638 | { 639 | _hcFileError(file, "Unexpected end-of-file."); 640 | return (false); 641 | } 642 | else if (ch != '>') 643 | _hcFileError(file, "Element too long."); 644 | 645 | return (_hcNodeNewUnknown(*parent, buffer) != NULL); 646 | } 647 | -------------------------------------------------------------------------------- /html-node.c: -------------------------------------------------------------------------------- 1 | // 2 | // HTML node functions for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #include "html-private.h" 13 | 14 | 15 | // 16 | // Local functions... 17 | // 18 | 19 | static void html_delete(hc_node_t *node); 20 | static hc_node_t *html_new(hc_node_t *parent, hc_element_t element, const char *s); 21 | static void html_remove(hc_node_t *node); 22 | 23 | 24 | // 25 | // 'hcNodeDelete()' - Delete a HTML node from a document. 26 | // 27 | 28 | void 29 | hcNodeDelete(hc_html_t *html, // I - HTML document 30 | hc_node_t *node) // I - HTML node 31 | { 32 | hc_node_t *current, // Current node 33 | *next; // Next node 34 | 35 | 36 | if (!html || !node) 37 | return; 38 | 39 | if (node == html->root) 40 | html->root = NULL; 41 | 42 | html_remove(node); 43 | 44 | for (current = node->value.element.first_child; current; current = next) 45 | { 46 | /* 47 | * Get the next node... 48 | */ 49 | 50 | if ((next = hcNodeGetFirstChildNode(current)) != NULL) 51 | { 52 | /* 53 | * Free parent nodes after child nodes have been freed... 54 | */ 55 | 56 | current->value.element.first_child = NULL; 57 | continue; 58 | } 59 | 60 | if ((next = current->next_sibling) == NULL) 61 | { 62 | /* 63 | * Next node is the parent, which we'll free as needed... 64 | */ 65 | 66 | if ((next = current->parent) == node) 67 | next = NULL; 68 | } 69 | 70 | /* 71 | * Free child... 72 | */ 73 | 74 | html_delete(current); 75 | } 76 | 77 | /* 78 | * Then free the memory used by the parent node... 79 | */ 80 | 81 | html_delete(node); 82 | } 83 | 84 | 85 | // 86 | // 'hcNodeGetComment()' - Get a HTML node's comment value, if any. 87 | // 88 | 89 | const char * // O - Comment value 90 | hcNodeGetComment(hc_node_t *node) // I - HTML node 91 | { 92 | return (node && node->element == HC_ELEMENT_COMMENT ? node->value.comment : NULL); 93 | } 94 | 95 | 96 | // 97 | // 'hcHTMLGetDOCTYPE()' - Get a HTML document's DOCTYPE value, if any. 98 | // 99 | 100 | const char * // O - DOCTYPE value 101 | hcHTMLGetDOCTYPE(hc_html_t *html) // I - HTML document 102 | { 103 | return (html && html->root ? hcNodeAttrGetNameValue(html->root, "") : NULL); 104 | } 105 | 106 | 107 | // 108 | // 'hcNodeGetElement()' - Get a HTML node's element/type. 109 | // 110 | 111 | hc_element_t // O - Node element/type 112 | hcNodeGetElement(hc_node_t *node) // I - HTML node 113 | { 114 | return (node ? node->element : HC_ELEMENT_WILDCARD); 115 | } 116 | 117 | 118 | // 119 | // 'hcNodeGetFirstChildNode()' - Get a HTML node's first child node, if any. 120 | // 121 | 122 | hc_node_t * // O - First child node or `NULL` if none 123 | hcNodeGetFirstChildNode(hc_node_t *node)// I - HTML node 124 | { 125 | return (node && node->element >= HC_ELEMENT_DOCTYPE ? node->value.element.first_child : NULL); 126 | } 127 | 128 | 129 | // 130 | // 'hcNodeGetLastChildNode()' - Get a HTML node's last child node, if any. 131 | // 132 | 133 | hc_node_t * // O - Last child node or `NULL` if none 134 | hcNodeGetLastChildNode(hc_node_t *node) // I - HTML node 135 | { 136 | return (node && node->element >= HC_ELEMENT_DOCTYPE ? node->value.element.last_child : NULL); 137 | } 138 | 139 | 140 | // 141 | // 'hcNodeGetNextSiblingNode()' - Get a HTML node's next sibling node, if any. 142 | // 143 | 144 | hc_node_t * // O - Next sibling node or `NULL` if none 145 | hcNodeGetNextSiblingNode( 146 | hc_node_t *node) // I - HTML node 147 | { 148 | return (node ? node->next_sibling : NULL); 149 | } 150 | 151 | 152 | // 153 | // 'hcNodeGetParentNode()' - Get a HTML node's parent node, if any. 154 | // 155 | 156 | hc_node_t * // O - Parent node or `NULL` if none 157 | hcNodeGetParentNode(hc_node_t *node) // I - HTML node 158 | { 159 | return (node ? node->parent : NULL); 160 | } 161 | 162 | 163 | // 164 | // 'hcNodeGetPrevSiblingNode()' - Get a HTML node's previous sibling node, if any. 165 | // 166 | 167 | hc_node_t * // O - Previous sibling node or `NULL` if none 168 | hcNodeGetPrevSiblingNode( 169 | hc_node_t *node) // I - HTML node 170 | { 171 | return (node ? node->prev_sibling : NULL); 172 | } 173 | 174 | 175 | // 176 | // 'hcHTMLGetRootNode()' - Get the root node for a document. 177 | // 178 | 179 | hc_node_t * // O - Root node or `NULL` if none 180 | hcHTMLGetRootNode(hc_html_t *html) // I - HTML document 181 | { 182 | return (html ? html->root : NULL); 183 | } 184 | 185 | 186 | // 187 | // 'hcNodeGetString()' - Get a HTML node's string value, if any. 188 | // 189 | 190 | const char * // O - String value 191 | hcNodeGetString(hc_node_t *node) // I - HTML node 192 | { 193 | return (node && node->element == HC_ELEMENT_STRING ? node->value.string : NULL); 194 | } 195 | 196 | 197 | // 198 | // 'hcNodeNewComment()' - Create a new HTML comment node. 199 | // 200 | 201 | hc_node_t * // O - New HTML comment node 202 | hcNodeNewComment(hc_node_t *parent, // I - Parent node 203 | const char *c) // I - Comment value 204 | { 205 | if (!parent || !c) 206 | return (NULL); 207 | 208 | return (html_new(parent, HC_ELEMENT_COMMENT, c)); 209 | } 210 | 211 | 212 | // 213 | // 'hcNodeNewElement()' - Create a new HTML element node. 214 | // 215 | 216 | hc_node_t * // O - New HTML element node 217 | hcNodeNewElement(hc_node_t *parent, // I - Parent node 218 | hc_element_t element) // I - HTML element 219 | { 220 | if (!parent || element <= HC_ELEMENT_DOCTYPE || element >= HC_ELEMENT_MAX) 221 | return (NULL); 222 | 223 | return (html_new(parent, element, NULL)); 224 | } 225 | 226 | 227 | // 228 | // 'hcHTMLNewRootNode()' - Create a new root node. 229 | // 230 | 231 | hc_node_t * // O - New root node 232 | hcHTMLNewRootNode(hc_html_t *html, // I - HTML document 233 | const char *doctype) // I - DOCTYPE value 234 | { 235 | hc_node_t *node; // New node 236 | 237 | 238 | if (!html || html->root || !doctype) 239 | return (NULL); 240 | 241 | if ((node = html_new(NULL, HC_ELEMENT_DOCTYPE, NULL)) != NULL) 242 | { 243 | html->root = node; 244 | node->value.element.html = html; 245 | 246 | hcNodeAttrSetNameValue(node, "", doctype); 247 | } 248 | 249 | return (node); 250 | } 251 | 252 | 253 | // 254 | // 'hcNodeNewString()' - Create a new HTML string node. 255 | // 256 | 257 | hc_node_t * // O - New HTML string node 258 | hcNodeNewString(hc_node_t *parent, // I - Parent node 259 | const char *s) // I - String value 260 | { 261 | if (!parent || !s) 262 | return (NULL); 263 | 264 | return (html_new(parent, HC_ELEMENT_STRING, s)); 265 | } 266 | 267 | 268 | // 269 | // '_hcNodeNewUnknown()' - Create a new unknown HTML element or processing 270 | // directive node. 271 | // 272 | 273 | hc_node_t * // O - New HTML unknown node 274 | _hcNodeNewUnknown(hc_node_t *parent, // I - Parent node 275 | const char *unk) // I - Unknown value (excluding "<>") 276 | { 277 | if (!parent || !unk) 278 | return (NULL); 279 | 280 | return (html_new(parent, HC_ELEMENT_UNKNOWN, unk)); 281 | } 282 | 283 | 284 | // 285 | // 'html_delete()' - Free a HTML node. 286 | // 287 | 288 | static void 289 | html_delete(hc_node_t *node) // I - HTML node 290 | { 291 | if (node->element >= HC_ELEMENT_DOCTYPE) 292 | hcDictDelete(node->value.element.attrs); 293 | 294 | free(node); 295 | } 296 | 297 | 298 | // 299 | // 'html_new()' - Create a new HTML node. 300 | // 301 | 302 | static hc_node_t * // O - New node or `NULL` on error 303 | html_new(hc_node_t *parent, // I - Parent node or `NULL` if root node 304 | hc_element_t element, // I - Element/node type 305 | const char *s) // I - String, if any 306 | { 307 | hc_node_t *node; // New node 308 | size_t nodesize; // Node size 309 | size_t slen = s ? strlen(s) : 0; 310 | // Length of string 311 | 312 | 313 | if (parent && parent->element < HC_ELEMENT_DOCTYPE) 314 | return (NULL); 315 | 316 | if (element < HC_ELEMENT_DOCTYPE) 317 | nodesize = sizeof(hc_node_t) - sizeof(node->value) + slen + 1; 318 | else 319 | nodesize = sizeof(hc_node_t); 320 | 321 | if ((node = (hc_node_t *)calloc(1, nodesize)) != NULL) 322 | { 323 | node->element = element; 324 | node->parent = parent; 325 | 326 | if (s && slen > 0) 327 | { 328 | if (element == HC_ELEMENT_STRING) 329 | memcpy(node->value.string, s, slen); 330 | else if (element == HC_ELEMENT_COMMENT) 331 | memcpy(node->value.comment, s, slen); 332 | else if (element == HC_ELEMENT_UNKNOWN) 333 | memcpy(node->value.unknown, s, slen); 334 | } 335 | 336 | if (parent) 337 | { 338 | if (element > HC_ELEMENT_DOCTYPE) 339 | node->value.element.html = parent->value.element.html; 340 | 341 | if (parent->value.element.last_child) 342 | { 343 | node->prev_sibling = parent->value.element.last_child; 344 | parent->value.element.last_child->next_sibling = node; 345 | parent->value.element.last_child = node; 346 | } 347 | else 348 | { 349 | parent->value.element.first_child = node; 350 | parent->value.element.last_child = node; 351 | } 352 | } 353 | } 354 | 355 | return (node); 356 | } 357 | 358 | 359 | // 360 | // 'html_remove()' - Remove a HTML node from its parent. 361 | // 362 | 363 | static void 364 | html_remove(hc_node_t *node) // I - HTML node 365 | { 366 | if (node->parent) 367 | { 368 | if (node->prev_sibling) 369 | node->prev_sibling->next_sibling = node->next_sibling; 370 | else 371 | node->parent->value.element.first_child = node->next_sibling; 372 | 373 | if (node->next_sibling) 374 | node->next_sibling->prev_sibling = node->prev_sibling; 375 | else 376 | node->parent->value.element.last_child = node->prev_sibling; 377 | 378 | node->parent = NULL; 379 | node->prev_sibling = NULL; 380 | node->next_sibling = NULL; 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /html-private.h: -------------------------------------------------------------------------------- 1 | // 2 | // Private HTML header file for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #ifndef HTMLCSS_HTML_PRIVATE_H 13 | # define HTMLCSS_HTML_PRIVATE_H 14 | # include "html.h" 15 | # include "common-private.h" 16 | # include "dict.h" 17 | # include "pool-private.h" 18 | # ifdef __cplusplus 19 | extern "C" { 20 | # endif // __cplusplus 21 | 22 | 23 | // 24 | // Private types... 25 | // 26 | 27 | struct _hc_node_s 28 | { 29 | hc_element_t element; // Element type 30 | hc_node_t *parent; // Parent node 31 | hc_node_t *prev_sibling; // Previous (sibling) node 32 | hc_node_t *next_sibling; // Next (sibling) node 33 | union 34 | { 35 | char comment[1]; // Comment value 36 | struct 37 | { 38 | hc_node_t *first_child; // First child node 39 | hc_node_t *last_child; // Last child node 40 | hc_dict_t *attrs; // Attributes dictionary 41 | hc_html_t *html; // HTML document 42 | const hc_dict_t *base_props; // Base CSS properties dictionary 43 | } element; // Element value 44 | char string[1]; // String value 45 | char unknown[1]; // Unknown element/directive value 46 | } value; // Node value 47 | }; 48 | 49 | struct _hc_html_s 50 | { 51 | hc_pool_t *pool; // Memory pool 52 | hc_css_t *css; // Stylesheet 53 | hc_node_t *root; // Root node 54 | hc_error_cb_t error_cb; // Error callback 55 | void *error_ctx; // Error callback context pointer 56 | hc_url_cb_t url_cb; // URL callback 57 | void *url_ctx; // URL callback context pointer 58 | }; 59 | 60 | 61 | // 62 | // Private functions... 63 | // 64 | 65 | extern bool _hcNodeComputeCSSTextFont(hc_node_t *node, const hc_dict_t *props, hc_text_t *text); 66 | extern hc_node_t *_hcNodeNewUnknown(hc_node_t *parent, const char *unk); 67 | 68 | 69 | # ifdef __cplusplus 70 | } 71 | # endif // __cplusplus 72 | #endif // !HTMLCSS_HTML_PRIVATE_H 73 | -------------------------------------------------------------------------------- /html.h: -------------------------------------------------------------------------------- 1 | // 2 | // HTML header file for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #ifndef HTMLCSS_HTML_H 13 | # define HTMLCSS_HTML_H 14 | # include "css.h" 15 | # ifdef __cplusplus 16 | extern "C" { 17 | # endif // __cplusplus 18 | 19 | 20 | // 21 | // Types... 22 | // 23 | 24 | typedef enum // HTML element enum 25 | { 26 | HC_ELEMENT_UNKNOWN = -2, // Unknown element/directive 27 | HC_ELEMENT_STRING = -1, // String 28 | HC_ELEMENT_WILDCARD, // Wildcard (*) 29 | HC_ELEMENT_COMMENT, // !-- 30 | HC_ELEMENT_DOCTYPE, // !DOCTYPE 31 | HC_ELEMENT_A, 32 | HC_ELEMENT_ABBR, 33 | HC_ELEMENT_ACRONYM, 34 | HC_ELEMENT_ADDRESS, 35 | HC_ELEMENT_APPLET, 36 | HC_ELEMENT_AREA, 37 | HC_ELEMENT_ARTICLE, 38 | HC_ELEMENT_ASIDE, 39 | HC_ELEMENT_AUDIO, 40 | HC_ELEMENT_B, 41 | HC_ELEMENT_BASE, 42 | HC_ELEMENT_BASEFONT, 43 | HC_ELEMENT_BDI, 44 | HC_ELEMENT_BDO, 45 | HC_ELEMENT_BIG, 46 | HC_ELEMENT_BLINK, 47 | HC_ELEMENT_BLOCKQUOTE, 48 | HC_ELEMENT_BODY, 49 | HC_ELEMENT_BR, 50 | HC_ELEMENT_BUTTON, 51 | HC_ELEMENT_CANVAS, 52 | HC_ELEMENT_CAPTION, 53 | HC_ELEMENT_CENTER, 54 | HC_ELEMENT_CITE, 55 | HC_ELEMENT_CODE, 56 | HC_ELEMENT_COL, 57 | HC_ELEMENT_COLGROUP, 58 | HC_ELEMENT_DATA, 59 | HC_ELEMENT_DATALIST, 60 | HC_ELEMENT_DD, 61 | HC_ELEMENT_DEL, 62 | HC_ELEMENT_DETAILS, 63 | HC_ELEMENT_DFN, 64 | HC_ELEMENT_DIALOG, 65 | HC_ELEMENT_DIR, 66 | HC_ELEMENT_DIV, 67 | HC_ELEMENT_DL, 68 | HC_ELEMENT_DT, 69 | HC_ELEMENT_EM, 70 | HC_ELEMENT_EMBED, 71 | HC_ELEMENT_FIELDSET, 72 | HC_ELEMENT_FIGCAPTION, 73 | HC_ELEMENT_FIGURE, 74 | HC_ELEMENT_FONT, 75 | HC_ELEMENT_FOOTER, 76 | HC_ELEMENT_FORM, 77 | HC_ELEMENT_FRAME, 78 | HC_ELEMENT_FRAMESET, 79 | HC_ELEMENT_H1, 80 | HC_ELEMENT_H2, 81 | HC_ELEMENT_H3, 82 | HC_ELEMENT_H4, 83 | HC_ELEMENT_H5, 84 | HC_ELEMENT_H6, 85 | HC_ELEMENT_HEAD, 86 | HC_ELEMENT_HEADER, 87 | HC_ELEMENT_HR, 88 | HC_ELEMENT_HTML, 89 | HC_ELEMENT_I, 90 | HC_ELEMENT_IFRAME, 91 | HC_ELEMENT_IMG, 92 | HC_ELEMENT_INPUT, 93 | HC_ELEMENT_INS, 94 | HC_ELEMENT_ISINDEX, 95 | HC_ELEMENT_KBD, 96 | HC_ELEMENT_LABEL, 97 | HC_ELEMENT_LEGEND, 98 | HC_ELEMENT_LI, 99 | HC_ELEMENT_LINK, 100 | HC_ELEMENT_MAIN, 101 | HC_ELEMENT_MAP, 102 | HC_ELEMENT_MARK, 103 | HC_ELEMENT_MENU, 104 | HC_ELEMENT_META, 105 | HC_ELEMENT_METER, 106 | HC_ELEMENT_MULTICOL, 107 | HC_ELEMENT_NAV, 108 | HC_ELEMENT_NOBR, 109 | HC_ELEMENT_NOFRAMES, 110 | HC_ELEMENT_NOSCRIPT, 111 | HC_ELEMENT_OBJECT, 112 | HC_ELEMENT_OL, 113 | HC_ELEMENT_OPTGROUP, 114 | HC_ELEMENT_OPTION, 115 | HC_ELEMENT_OUTPUT, 116 | HC_ELEMENT_P, 117 | HC_ELEMENT_PARAM, 118 | HC_ELEMENT_PICTURE, 119 | HC_ELEMENT_PRE, 120 | HC_ELEMENT_PROGRESS, 121 | HC_ELEMENT_Q, 122 | HC_ELEMENT_RB, 123 | HC_ELEMENT_RP, 124 | HC_ELEMENT_RT, 125 | HC_ELEMENT_RTC, 126 | HC_ELEMENT_RUBY, 127 | HC_ELEMENT_S, 128 | HC_ELEMENT_SAMP, 129 | HC_ELEMENT_SCRIPT, 130 | HC_ELEMENT_SECTION, 131 | HC_ELEMENT_SELECT, 132 | HC_ELEMENT_SMALL, 133 | HC_ELEMENT_SOURCE, 134 | HC_ELEMENT_SPACER, 135 | HC_ELEMENT_SPAN, 136 | HC_ELEMENT_STRIKE, 137 | HC_ELEMENT_STRONG, 138 | HC_ELEMENT_STYLE, 139 | HC_ELEMENT_SUB, 140 | HC_ELEMENT_SUMMARY, 141 | HC_ELEMENT_SUP, 142 | HC_ELEMENT_TABLE, 143 | HC_ELEMENT_TBODY, 144 | HC_ELEMENT_TD, 145 | HC_ELEMENT_TEMPLATE, 146 | HC_ELEMENT_TEXTAREA, 147 | HC_ELEMENT_TFOOT, 148 | HC_ELEMENT_TH, 149 | HC_ELEMENT_THEAD, 150 | HC_ELEMENT_TIME, 151 | HC_ELEMENT_TITLE, 152 | HC_ELEMENT_TR, 153 | HC_ELEMENT_TRACK, 154 | HC_ELEMENT_TT, 155 | HC_ELEMENT_U, 156 | HC_ELEMENT_UL, 157 | HC_ELEMENT_VAR, 158 | HC_ELEMENT_VIDEO, 159 | HC_ELEMENT_WBR, 160 | HC_ELEMENT_MAX 161 | } hc_element_t; 162 | 163 | typedef struct _hc_node_s hc_node_t; // HTML node 164 | 165 | typedef struct _hc_html_s hc_html_t; // HTML document 166 | 167 | 168 | // 169 | // Functions... 170 | // 171 | 172 | extern const char *hcElementString(hc_element_t e) _HC_PUBLIC; 173 | extern hc_element_t hcElementValue(const char *s) _HC_PUBLIC; 174 | 175 | extern void hcHTMLDelete(hc_html_t *html) _HC_PUBLIC; 176 | extern hc_node_t *hcHTMLFindNode(hc_html_t *html, hc_node_t *current, hc_element_t element, const char *id) _HC_PUBLIC; 177 | extern hc_css_t *hcHTMLGetCSS(hc_html_t *html) _HC_PUBLIC; 178 | extern const char *hcHTMLGetDOCTYPE(hc_html_t *html) _HC_PUBLIC; 179 | extern hc_node_t *hcHTMLGetRootNode(hc_html_t *html) _HC_PUBLIC; 180 | extern bool hcHTMLImport(hc_html_t *html, hc_file_t *file) _HC_PUBLIC; 181 | extern hc_html_t *hcHTMLNew(hc_pool_t *pool, hc_css_t *css) _HC_PUBLIC; 182 | extern hc_node_t *hcHTMLNewRootNode(hc_html_t *html, const char *doctype) _HC_PUBLIC; 183 | extern void hcHTMLSetErrorCallback(hc_html_t *html, hc_error_cb_t cb, void *cbdata) _HC_PUBLIC; 184 | extern void hcHTMLSetURLCallback(hc_html_t *html, hc_url_cb_t cb, void *cbdata) _HC_PUBLIC; 185 | 186 | extern size_t hcNodeAttrGetCount(hc_node_t *node) _HC_PUBLIC; 187 | extern const char *hcNodeAttrGetIndexNameValue(hc_node_t *node, size_t idx, const char **name) _HC_PUBLIC; 188 | extern const char *hcNodeAttrGetNameValue(hc_node_t *node, const char *name) _HC_PUBLIC; 189 | extern void hcNodeAttrRemove(hc_node_t *node, const char *name) _HC_PUBLIC; 190 | extern void hcNodeAttrSetNameValue(hc_node_t *node, const char *name, const char *value) _HC_PUBLIC; 191 | 192 | extern bool hcNodeComputeCSSBox(hc_node_t *node, hc_compute_t compute, hc_box_t *box) _HC_PUBLIC; 193 | extern char *hcNodeComputeCSSContent(hc_node_t *node, hc_compute_t compute) _HC_PUBLIC; 194 | extern hc_display_t hcNodeComputeCSSDisplay(hc_node_t *node, hc_compute_t compute) _HC_PUBLIC; 195 | extern bool hcNodeComputeCSSMedia(hc_node_t *node, hc_compute_t compute, hc_media_t *media) _HC_PUBLIC; 196 | extern const hc_dict_t *hcNodeComputeCSSProperties(hc_node_t *node, hc_compute_t compute) _HC_PUBLIC; 197 | extern bool hcNodeComputeCSSTable(hc_node_t *node, hc_compute_t compute, hc_table_t *table) _HC_PUBLIC; 198 | extern bool hcNodeComputeCSSText(hc_node_t *node, hc_compute_t compute, hc_text_t *text) _HC_PUBLIC; 199 | 200 | extern void hcNodeDelete(hc_html_t *html, hc_node_t *node) _HC_PUBLIC; 201 | extern const char *hcNodeGetComment(hc_node_t *node) _HC_PUBLIC; 202 | extern hc_element_t hcNodeGetElement(hc_node_t *node) _HC_PUBLIC; 203 | extern hc_node_t *hcNodeGetFirstChildNode(hc_node_t *node) _HC_PUBLIC; 204 | extern hc_node_t *hcNodeGetLastChildNode(hc_node_t *node) _HC_PUBLIC; 205 | extern hc_node_t *hcNodeGetNextSiblingNode(hc_node_t *node) _HC_PUBLIC; 206 | extern hc_node_t *hcNodeGetParentNode(hc_node_t *node) _HC_PUBLIC; 207 | extern hc_node_t *hcNodeGetPrevSiblingNode(hc_node_t *node) _HC_PUBLIC; 208 | extern const char *hcNodeGetString(hc_node_t *node) _HC_PUBLIC; 209 | extern hc_node_t *hcNodeNewComment(hc_node_t *parent, const char *c) _HC_PUBLIC; 210 | extern hc_node_t *hcNodeNewElement(hc_node_t *parent, hc_element_t element) _HC_PUBLIC; 211 | extern hc_node_t *hcNodeNewString(hc_node_t *parent, const char *s) _HC_PUBLIC; 212 | 213 | 214 | # ifdef __cplusplus 215 | } 216 | # endif // __cplusplus 217 | #endif // !HTMLCSS_HTML_H 218 | -------------------------------------------------------------------------------- /htmlcss-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelrsweet/htmlcss/a036c2d245d82e56f1291579019966d2cad19b23/htmlcss-512.png -------------------------------------------------------------------------------- /htmlcss.h: -------------------------------------------------------------------------------- 1 | // 2 | // Main header file for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #ifndef HTMLCSS_H 13 | # define HTMLCSS_H 14 | # include "font.h" 15 | # include "image.h" 16 | # include "html.h" 17 | # include "run.h" 18 | #endif // !HTMLCSS_H 19 | -------------------------------------------------------------------------------- /htmlcss.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@prefix@ 2 | exec_prefix=@exec_prefix@ 3 | libdir=@libdir@ 4 | includedir=@includedir@ 5 | 6 | Name: pdfio 7 | Description: PDF read/write library 8 | Version: @PDFIO_VERSION@ 9 | URL: https://www.msweet.org/pdfio 10 | Cflags: @PKGCONFIG_CFLAGS@ 11 | Libs: @PKGCONFIG_LIBS@ 12 | Libs.private: @PKGCONFIG_LIBS_PRIVATE@ 13 | Requires: @PKGCONFIG_REQUIRES@ 14 | -------------------------------------------------------------------------------- /htmlcss.svg: -------------------------------------------------------------------------------- 1 | 2 | 15 | 17 | 35 | 37 | 38 | 40 | New Image 41 | Michael Sweet 42 | Copyright 2017 Michael Sweet 43 | 44 | image/svg+xml 45 | en 46 | 47 | 48 | 49 | 51 | Base 53 | 61 | 69 | 77 | 85 | 93 | 101 | 109 | 110 | 118 | 123 | 128 | 129 | -------------------------------------------------------------------------------- /htmlcss.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /htmlcss.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /image.c: -------------------------------------------------------------------------------- 1 | // 2 | // Image handling functions for HTMLCSS library. 3 | // 4 | // Note: The purpose of these functions is currently just to discover the 5 | // dimensions and format of an image file and not to decode or transform the 6 | // contents of an image. 7 | // 8 | // https://github.com/michaelrsweet/htmlcss 9 | // 10 | // Copyright © 2019-2025 by Michael R Sweet. 11 | // 12 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 13 | // information. 14 | // 15 | 16 | #include "image.h" 17 | #include "common-private.h" 18 | 19 | 20 | // 21 | // Types... 22 | // 23 | 24 | typedef enum _hc_res_e 25 | { 26 | _HC_RES_NONE, // No units 27 | _HC_RES_PER_INCH, // Pixels per inch 28 | _HC_RES_PER_CM // Pixels per cm 29 | } _hc_res_t; 30 | 31 | struct _hc_image_s 32 | { 33 | hc_pool_t *pool; // Memory pool 34 | const char *format; // MIME media type 35 | int width, // Width in pixels 36 | height; // Height in pixels 37 | int xres, // Width resolution 38 | yres; // Height resolution 39 | _hc_res_t units; // Resolution units 40 | }; 41 | 42 | 43 | // 44 | // Local functions... 45 | // 46 | 47 | 48 | // 49 | // 'hcImageDelete()' - Delete an image object. 50 | // 51 | 52 | void 53 | hcImageDelete(hc_image_t *image) // I - Image object 54 | { 55 | free(image); 56 | } 57 | 58 | 59 | // 60 | // 'hcImageGetFormat()' - Get the MIME media type for the image. 61 | // 62 | 63 | const char * // O - MIME media type 64 | hcImageGetFormat(hc_image_t *image) // I - Image object 65 | { 66 | return (image ? image->format : NULL); 67 | } 68 | 69 | 70 | // 71 | // 'hcImageGetHeight()' - Get the height of an image. 72 | // 73 | 74 | int // O - Height in pixels 75 | hcImageGetHeight(hc_image_t *image) // I - Image object 76 | { 77 | return (image ? image->height : 0); 78 | } 79 | 80 | 81 | // 82 | // 'hcImageGetSize()' - Get the natural size of an image. 83 | // 84 | 85 | hc_size_t // O - CSS dimensions 86 | hcImageGetSize(hc_image_t *image) // I - Image object 87 | { 88 | hc_size_t size; // CSS dimensions 89 | 90 | 91 | if (image) 92 | { 93 | if (image->xres > 0 && image->yres > 0 && image->units != _HC_RES_NONE) 94 | { 95 | if (image->units == _HC_RES_PER_INCH) 96 | { 97 | /* 98 | * Convert from PPI to points... 99 | */ 100 | 101 | size.width = 72.0f * image->width / image->xres; 102 | size.height = 72.0f * image->height / image->yres; 103 | } 104 | else 105 | { 106 | /* 107 | * Convert from PPCM to points... 108 | */ 109 | 110 | size.width = 72.0f / 2.54f * image->width / image->xres; 111 | size.height = 72.0f / 2.54f * image->height / image->yres; 112 | } 113 | } 114 | else 115 | { 116 | /* 117 | * Default resolution is 100 PPI, CSS units are points (72 points per inch). 118 | */ 119 | 120 | size.width = 0.72f * image->width; 121 | size.height = 0.72f * image->height; 122 | } 123 | } 124 | else 125 | { 126 | size.width = 0.0f; 127 | size.height = 0.0f; 128 | } 129 | 130 | return (size); 131 | } 132 | 133 | 134 | // 135 | // 'hcImageGetWidth()' - Get the width of an image. 136 | // 137 | 138 | int // O - Width in pixels 139 | hcImageGetWidth(hc_image_t *image) // I - Image object 140 | { 141 | return (image ? image->width : 0); 142 | } 143 | 144 | 145 | // 146 | // 'hcImageNew()' - Create a new image object. 147 | // 148 | 149 | hc_image_t * // O - Image object 150 | hcImageNew(hc_pool_t *pool, // I - Memory pool 151 | hc_file_t *file) // I - File 152 | { 153 | hc_image_t *image; // Image object 154 | unsigned char buffer[2048]; // Buffer 155 | size_t bytes; // Bytes in buffer 156 | 157 | 158 | _HC_DEBUG("hcImageNew(pool=%p, file=%p)\n", (void *)pool, (void *)file); 159 | 160 | if (!pool || !file) 161 | return (NULL); 162 | 163 | if ((image = (hc_image_t *)calloc(1, sizeof(hc_image_t))) == NULL) 164 | return (NULL); 165 | 166 | image->pool = pool; 167 | 168 | bytes = hcFileRead(file, buffer, sizeof(buffer)); 169 | 170 | _HC_DEBUG("hcImageNew: Read %d bytes from file.\n", (int)bytes); 171 | _HC_DEBUG("hcImageNew: \\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], buffer[15]); 172 | 173 | if (bytes > 27 && !memcmp(buffer, "\211PNG\015\012\032\012\000\000\000\015IHDR", 16)) 174 | { 175 | /* 176 | * PNG image... 177 | */ 178 | 179 | image->format = "image/png"; 180 | image->width = (buffer[16] << 24) | (buffer[17] << 16) | (buffer[18] << 8) | buffer[19]; 181 | image->height = (buffer[20] << 24) | (buffer[21] << 16) | (buffer[22] << 8) | buffer[23]; 182 | } 183 | else if (bytes > 12 && (!memcmp(buffer, "GIF87a", 6) || !memcmp(buffer, "GIF89a", 6))) 184 | { 185 | /* 186 | * GIF image... 187 | */ 188 | 189 | image->format = "image/gif"; 190 | image->width = (buffer[7] << 8) | buffer[6]; 191 | image->height = (buffer[9] << 8) | buffer[8]; 192 | } 193 | else if (bytes > 3 && !memcmp(buffer, "\377\330\377", 3)) 194 | { 195 | /* 196 | * JPEG image... 197 | */ 198 | 199 | unsigned char *bufptr = buffer + 2, 200 | // Pointer into buffer 201 | *bufend = buffer + bytes; 202 | // End of buffer 203 | size_t length; // Length of marker 204 | 205 | image->format = "image/jpeg"; 206 | 207 | /* 208 | * Scan the file for a SOFn marker, then we can get the dimensions... 209 | */ 210 | 211 | while (bufptr < bufend) 212 | { 213 | if (*bufptr == 0xff) 214 | { 215 | bufptr ++; 216 | 217 | if (bufptr >= bufend) 218 | { 219 | /* 220 | * If we are at the end of the current buffer, re-fill and continue... 221 | */ 222 | 223 | if ((bytes = hcFileRead(file, buffer, sizeof(buffer))) == 0) 224 | break; 225 | 226 | bufptr = buffer; 227 | bufend = buffer + bytes; 228 | } 229 | 230 | if (*bufptr == 0xff) 231 | continue; 232 | 233 | if ((bufptr + 16) >= bufend) 234 | { 235 | /* 236 | * Read more of the marker... 237 | */ 238 | 239 | bytes = (size_t)(bufend - bufptr); 240 | 241 | memmove(buffer, bufptr, bytes); 242 | bufptr = buffer; 243 | bufend = buffer + bytes; 244 | 245 | if ((bytes = hcFileRead(file, bufend, sizeof(buffer) - bytes)) == 0) 246 | break; 247 | 248 | bufend += bytes; 249 | } 250 | 251 | length = (size_t)((bufptr[1] << 8) | bufptr[2]); 252 | 253 | _HC_DEBUG("hcImageNew: JPEG X'FF%02X' (length %u)\n", *bufptr, (unsigned)length); 254 | 255 | if (*bufptr == 0xe0 && length >= 16 && !memcmp(bufptr + 3, "JFIF", 5)) 256 | { 257 | /* 258 | * APP0 marker for JFIF... 259 | */ 260 | 261 | if (bufptr[10] == 1) 262 | { 263 | image->units = _HC_RES_PER_INCH; 264 | image->xres = (bufptr[11] << 8) | bufptr[12]; 265 | image->yres = (bufptr[13] << 8) | bufptr[14]; 266 | } 267 | else if (bufptr[10] == 2) 268 | { 269 | image->units = _HC_RES_PER_CM; 270 | image->xres = (bufptr[11] << 8) | bufptr[12]; 271 | image->yres = (bufptr[13] << 8) | bufptr[14]; 272 | } 273 | } 274 | else if ((*bufptr >= 0xc0 && *bufptr <= 0xc3) || 275 | (*bufptr >= 0xc5 && *bufptr <= 0xc7) || 276 | (*bufptr >= 0xc9 && *bufptr <= 0xcb) || 277 | (*bufptr >= 0xcd && *bufptr <= 0xcf)) 278 | { 279 | /* 280 | * SOFn marker, look for dimensions... 281 | */ 282 | 283 | image->width = (bufptr[6] << 8) | bufptr[7]; 284 | image->height = (bufptr[4] << 8) | bufptr[5]; 285 | break; 286 | } 287 | 288 | /* 289 | * Skip past this marker... 290 | */ 291 | 292 | bufptr ++; 293 | bytes = (size_t)(bufend - bufptr); 294 | 295 | while (length >= bytes) 296 | { 297 | length -= bytes; 298 | 299 | if ((bytes = hcFileRead(file, buffer, sizeof(buffer))) == 0) 300 | break; 301 | 302 | bufptr = buffer; 303 | bufend = buffer + bytes; 304 | } 305 | 306 | if (length > bytes) 307 | break; 308 | 309 | bufptr += length; 310 | } 311 | } 312 | 313 | if (image->width == 0 || image->height == 0) 314 | { 315 | free(image); 316 | return (NULL); 317 | } 318 | } 319 | else 320 | { 321 | free(image); 322 | return (NULL); 323 | } 324 | 325 | return (image); 326 | } 327 | -------------------------------------------------------------------------------- /image.h: -------------------------------------------------------------------------------- 1 | // 2 | // Image handling header for HTMLCSS library. 3 | // 4 | // Note: The purpose of these functions is currently just to discover the 5 | // dimensions and format of an image file and not to decode or transform the 6 | // contents of an image. 7 | // 8 | // https://github.com/michaelrsweet/htmlcss 9 | // 10 | // Copyright © 2019-2025 by Michael R Sweet. 11 | // 12 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 13 | // information. 14 | // 15 | 16 | #ifndef HTMLCSS_IMAGE_H 17 | # define HTMLCSS_IMAGE_H 18 | # include "css.h" 19 | # ifdef __cplusplus 20 | extern "C" { 21 | # endif // __cplusplus 22 | 23 | 24 | // 25 | // Types... 26 | // 27 | 28 | typedef struct _hc_image_s hc_image_t; // Image object 29 | 30 | 31 | // 32 | // Functions... 33 | // 34 | 35 | extern void hcImageDelete(hc_image_t *image); 36 | extern const char *hcImageGetFormat(hc_image_t *image); 37 | extern int hcImageGetHeight(hc_image_t *image); 38 | extern hc_size_t hcImageGetSize(hc_image_t *image); 39 | extern int hcImageGetWidth(hc_image_t *image); 40 | extern hc_image_t *hcImageNew(hc_pool_t *pool, hc_file_t *file); 41 | 42 | 43 | # ifdef __cplusplus 44 | } 45 | # endif // __cplusplus 46 | #endif // !HTMLCSS_IMAGE_H 47 | -------------------------------------------------------------------------------- /install-sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Install a program, script, or datafile. 4 | # 5 | # Copyright 2008-2012 by Apple Inc. 6 | # 7 | # This script is not compatible with BSD (or any other) install program, as it 8 | # allows owner and group changes to fail with a warning and makes sure that the 9 | # destination directory permissions are as specified - BSD install and the 10 | # original X11 install script did not change permissions of existing 11 | # directories. It also does not support the transform options since CUPS does 12 | # not use them... 13 | # 14 | # Original script from X11R5 (mit/util/scripts/install.sh) 15 | # Copyright 1991 by the Massachusetts Institute of Technology 16 | # 17 | # Permission to use, copy, modify, distribute, and sell this software and its 18 | # documentation for any purpose is hereby granted without fee, provided that 19 | # the above copyright notice appear in all copies and that both that 20 | # copyright notice and this permission notice appear in supporting 21 | # documentation, and that the name of M.I.T. not be used in advertising or 22 | # publicity pertaining to distribution of the software without specific, 23 | # written prior permission. M.I.T. makes no representations about the 24 | # suitability of this software for any purpose. It is provided "as is" 25 | # without express or implied warranty. 26 | # 27 | # Calling this script install-sh is preferred over install.sh, to prevent 28 | # `make' implicit rules from creating a file called install from it 29 | # when there is no Makefile. 30 | 31 | # set DOITPROG to echo to test this script 32 | # Don't use :- since 4.3BSD and earlier shells don't like it. 33 | doit="${DOITPROG-}" 34 | 35 | # Force umask to 022... 36 | umask 022 37 | 38 | # put in absolute paths if you don't have them in your path; or use env. vars. 39 | mvprog="${MVPROG-mv}" 40 | cpprog="${CPPROG-cp}" 41 | chmodprog="${CHMODPROG-chmod}" 42 | chownprog="${CHOWNPROG-chown}" 43 | chgrpprog="${CHGRPPROG-chgrp}" 44 | stripprog="${STRIPPROG-strip}" 45 | rmprog="${RMPROG-rm}" 46 | mkdirprog="${MKDIRPROG-mkdir}" 47 | gzipprog="${GZIPPROG-gzip}" 48 | 49 | transformbasename="" 50 | transform_arg="" 51 | instcmd="$mvprog" 52 | chmodcmd="$chmodprog 0755" 53 | chowncmd="" 54 | chgrpcmd="" 55 | stripcmd="" 56 | rmcmd="$rmprog -f" 57 | mvcmd="$mvprog" 58 | src="" 59 | dst="" 60 | dir_arg="" 61 | 62 | gzipcp() { 63 | # gzipcp from to 64 | $gzipprog -9 <"$1" >"$2" 65 | } 66 | 67 | while [ x"$1" != x ]; do 68 | case $1 in 69 | -c) 70 | instcmd="$cpprog" 71 | shift 72 | continue 73 | ;; 74 | 75 | -d) 76 | dir_arg=true 77 | shift 78 | continue 79 | ;; 80 | 81 | -m) 82 | chmodcmd="$chmodprog $2" 83 | shift 84 | shift 85 | continue 86 | ;; 87 | 88 | -o) 89 | chowncmd="$chownprog $2" 90 | shift 91 | shift 92 | continue 93 | ;; 94 | 95 | -g) 96 | chgrpcmd="$chgrpprog $2" 97 | shift 98 | shift 99 | continue 100 | ;; 101 | 102 | -s) 103 | stripcmd="$stripprog" 104 | shift 105 | continue 106 | ;; 107 | 108 | -z) 109 | instcmd="gzipcp" 110 | shift 111 | continue 112 | ;; 113 | 114 | *) 115 | if [ x"$src" = x ]; then 116 | src="$1" 117 | else 118 | dst="$1" 119 | fi 120 | shift 121 | continue 122 | ;; 123 | esac 124 | done 125 | 126 | if [ x"$src" = x ]; then 127 | echo "install-sh: No input file specified" 128 | exit 1 129 | fi 130 | 131 | if [ x"$dir_arg" != x ]; then 132 | dst="$src" 133 | src="" 134 | 135 | if [ -d "$dst" ]; then 136 | instcmd=: 137 | else 138 | instcmd=$mkdirprog 139 | fi 140 | else 141 | # Waiting for this to be detected by the "$instcmd $src $dsttmp" command 142 | # might cause directories to be created, which would be especially bad 143 | # if $src (and thus $dsttmp) contains '*'. 144 | if [ ! -f "$src" -a ! -d "$src" ]; then 145 | echo "install: $src does not exist" 146 | exit 1 147 | fi 148 | 149 | if [ x"$dst" = x ]; then 150 | echo "install: No destination specified" 151 | exit 1 152 | fi 153 | 154 | # If destination is a directory, append the input filename. 155 | if [ -d "$dst" ]; then 156 | dst="$dst/`basename $src`" 157 | fi 158 | fi 159 | 160 | ## this sed command emulates the dirname command 161 | dstdir="`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`" 162 | 163 | # Make sure that the destination directory exists. 164 | # This part is taken from Noah Friedman's mkinstalldirs script 165 | 166 | # Skip lots of stat calls in the usual case. 167 | if [ ! -d "$dstdir" ]; then 168 | defaultIFS=' 169 | ' 170 | IFS="${IFS-${defaultIFS}}" 171 | 172 | oIFS="${IFS}" 173 | # Some sh's can't handle IFS=/ for some reason. 174 | IFS='%' 175 | set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` 176 | IFS="${oIFS}" 177 | 178 | pathcomp='' 179 | 180 | while [ $# -ne 0 ] ; do 181 | pathcomp="${pathcomp}${1}" 182 | shift 183 | 184 | if [ ! -d "${pathcomp}" ]; then $doit $mkdirprog "${pathcomp}"; fi 185 | 186 | pathcomp="${pathcomp}/" 187 | done 188 | fi 189 | 190 | if [ x"$dir_arg" != x ]; then 191 | # Make a directory... 192 | $doit $instcmd $dst || exit 1 193 | 194 | # Allow chown/chgrp to fail, but log a warning 195 | if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst || echo "warning: Unable to change owner of $dst!"; fi 196 | if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst || echo "warning: Unable to change group of $dst!"; fi 197 | if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst || exit 1; fi 198 | else 199 | # Install a file... 200 | dstfile="`basename $dst`" 201 | 202 | # Check the destination file - for libraries just use the "-x" option 203 | # to strip... 204 | case "$dstfile" in 205 | *.a | *.dylib | *.sl | *.sl.* | *.so | *.so.*) 206 | stripopt="-x" 207 | ;; 208 | *) 209 | stripopt="" 210 | ;; 211 | esac 212 | 213 | # Make a temp file name in the proper directory. 214 | dsttmp="$dstdir/#inst.$$#" 215 | 216 | # Move or copy the file name to the temp name 217 | $doit $instcmd $src $dsttmp || exit 1 218 | 219 | # Update permissions and strip as needed, then move to the final name. 220 | # If the chmod, strip, rm, or mv commands fail, remove the installed 221 | # file... 222 | if [ x"$stripcmd" != x ]; then $doit $stripcmd $stripopt "$dsttmp" || echo "warning: Unable to strip $dst!"; fi 223 | if [ x"$chowncmd" != x ]; then $doit $chowncmd "$dsttmp" || echo "warning: Unable to change owner of $dst!"; fi 224 | if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd "$dsttmp" || echo "warning: Unable to change group of $dst!"; fi 225 | 226 | trap "rm -f ${dsttmp}" 0 && 227 | if [ x"$chmodcmd" != x ]; then $doit $chmodcmd "$dsttmp"; fi && 228 | $doit $rmcmd -f "$dstdir/$dstfile" && 229 | $doit $mvcmd "$dsttmp" "$dstdir/$dstfile" 230 | fi 231 | 232 | exit 0 233 | -------------------------------------------------------------------------------- /pool-private.h: -------------------------------------------------------------------------------- 1 | // 2 | // Private memory pool header for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #ifndef HTMLCSS_POOL_PRIVATE_H 13 | # define HTMLCSS_POOL_PRIVATE_H 14 | # include "common-private.h" 15 | # include "dict.h" 16 | # include 17 | # include 18 | # ifdef __cplusplus 19 | extern "C" { 20 | # endif // __cplusplus 21 | 22 | 23 | // 24 | // Types... 25 | // 26 | 27 | typedef struct _hc_font_info_s _hc_font_info_t; 28 | // Font cache information 29 | 30 | struct _hc_pool_s // Memory pool 31 | { 32 | struct lconv *loc; // Locale information 33 | size_t loc_declen; // Length of decimal point 34 | 35 | bool fonts_loaded; // Did we load the fonts? 36 | size_t num_fonts; // Number of fonts in pool 37 | size_t alloc_fonts; // Allocated size of fonts array 38 | _hc_font_info_t *fonts; // Fonts array 39 | size_t font_index[256]; // Index into fonts array 40 | 41 | size_t num_strings; // Number of strings in pool 42 | size_t alloc_strings; // Allocated size of strings array 43 | char **strings; // Strings array 44 | 45 | hc_dict_t *urls; // URLs mapped to local files 46 | 47 | hc_error_cb_t error_cb; // Error callback 48 | void *error_ctx; // Error callback context pointer 49 | char *last_error; // Last error message 50 | 51 | hc_url_cb_t url_cb; // URL callback 52 | void *url_ctx; // URL callback context pointer 53 | }; 54 | 55 | 56 | // 57 | // Functions... 58 | // 59 | 60 | extern void _hcPoolDeleteFonts(hc_pool_t *pool); 61 | extern bool _hcPoolError(hc_pool_t *pool, int linenum, const char *message, ...) _HC_FORMAT_ARGS(3, 4); 62 | extern bool _hcPoolErrorv(hc_pool_t *pool, int linenum, const char *message, va_list ap); 63 | 64 | 65 | # ifdef __cplusplus 66 | } 67 | # endif // __cplusplus 68 | #endif // !HTMLCSS_POOL_PRIVATE_H 69 | -------------------------------------------------------------------------------- /pool.c: -------------------------------------------------------------------------------- 1 | // 2 | // Memory pool functions for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #include "pool-private.h" 13 | 14 | 15 | // 16 | // Local functions... 17 | // 18 | 19 | static int compare_strings(char **a, char **b); 20 | 21 | 22 | // 23 | // 'hcPoolDelete()' - Free the memory used by a pool. 24 | // 25 | 26 | void 27 | hcPoolDelete(hc_pool_t *pool) // I - Memory pool 28 | { 29 | if (pool) 30 | { 31 | if (pool->num_fonts > 0) 32 | _hcPoolDeleteFonts(pool); 33 | 34 | if (pool->num_strings > 0) 35 | { 36 | size_t i; // Looping var 37 | char **temp; // String pointer 38 | 39 | for (i = pool->num_strings, temp = pool->strings; i > 0; i --, temp ++) 40 | free(*temp); 41 | 42 | free(pool->strings); 43 | } 44 | 45 | free(pool->last_error); 46 | free(pool); 47 | } 48 | } 49 | 50 | 51 | // 52 | // '_hcPoolError()' - Display an error message. 53 | // 54 | 55 | bool // O - `true` to continue, `false` to stop 56 | _hcPoolError( 57 | hc_pool_t *pool, // I - Memory pool 58 | int linenum, // I - Line number in file or 0 59 | const char *message, // I - Printf-style message string 60 | ...) // I - Additional arguments as needed 61 | { 62 | bool ret; // Return value 63 | va_list ap; // Pointer to additional arguments 64 | 65 | 66 | va_start(ap, message); 67 | ret = _hcPoolErrorv(pool, linenum, message, ap); 68 | va_end(ap); 69 | 70 | return (ret); 71 | } 72 | 73 | 74 | // 75 | // '_hcPoolErrorv()' - Display an error message. 76 | // 77 | 78 | bool // O - `true` to continue, `false` to stop 79 | _hcPoolErrorv( 80 | hc_pool_t *pool, // I - Memory pool 81 | int linenum, // I - Line number in file or 0 82 | const char *message, // I - Printf-style message string 83 | va_list ap) // I - Pointer to additional arguments 84 | { 85 | char buffer[8192]; // Message buffer 86 | 87 | 88 | vsnprintf(buffer, sizeof(buffer), message, ap); 89 | 90 | free(pool->last_error); 91 | pool->last_error = strdup(buffer); 92 | 93 | return ((pool->error_cb)(pool->error_ctx, buffer, linenum)); 94 | } 95 | 96 | 97 | // 98 | // 'hcPoolGetLastError()' - Return the last error message recorded. 99 | // 100 | 101 | const char * // O - Last error message or `NULL` 102 | hcPoolGetLastError(hc_pool_t *pool) // I - Memory pool 103 | { 104 | return (pool ? pool->last_error : NULL); 105 | } 106 | 107 | 108 | // 109 | // 'hcPoolGetString()' - Find or copy a string. 110 | // 111 | // This function finds or makes a copy of the passed string that will be freed 112 | // when the corresponding memory pool is deleted. Since the memory pool only 113 | // maintains a single copy of any string, copied strings are immutable. 114 | // 115 | 116 | const char * // O - New string pointer 117 | hcPoolGetString( 118 | hc_pool_t *pool, // I - Memory pool 119 | const char *s) // I - String to find/copy 120 | { 121 | char *news, // New string 122 | **temp; // Temporary string pointer 123 | 124 | 125 | if (!pool || !s) 126 | return (NULL); 127 | else if (!*s) 128 | return (""); 129 | 130 | if (pool->num_strings == 1 && !strcmp(pool->strings[0], s)) 131 | { 132 | _HC_DEBUG("hcPoolGetString: Existing string '%s' (%p) found.\n", pool->strings[0], (void *)pool->strings[0]); 133 | return (pool->strings[0]); 134 | } 135 | else if (pool->num_strings > 1) 136 | { 137 | if ((temp = bsearch(&s, pool->strings, pool->num_strings, sizeof(char *), (_hc_compare_func_t)compare_strings)) != NULL) 138 | { 139 | _HC_DEBUG("hcPoolGetString: Existing string '%s' (%p) found.\n", *temp, (void *)*temp); 140 | return (*temp); 141 | } 142 | } 143 | 144 | if (pool->num_strings >= pool->alloc_strings) 145 | { 146 | if ((temp = realloc(pool->strings, (pool->alloc_strings + 32) * sizeof(char *))) == NULL) 147 | return (NULL); 148 | 149 | pool->alloc_strings += 32; 150 | pool->strings = temp; 151 | } 152 | 153 | temp = pool->strings + pool->num_strings; 154 | *temp = news = strdup(s); 155 | pool->num_strings ++; 156 | 157 | if (pool->num_strings > 1) 158 | qsort(pool->strings, pool->num_strings, sizeof(char *), (_hc_compare_func_t)compare_strings); 159 | 160 | _HC_DEBUG("hcPoolGetString: New string '%s' (%p), pool now contains %d strings.\n", news, (void *)news, (int)pool->num_strings); 161 | 162 | return (news); 163 | } 164 | 165 | 166 | // 167 | // 'hcPoolGetURL()' - Get a file corresponding to a URL. 168 | // 169 | 170 | const char * // O - Filename or `NULL` on error 171 | hcPoolGetURL(hc_pool_t *pool, // I - Memory pool 172 | const char *url, // I - URL 173 | const char *baseurl) // I - Base URL, if any 174 | { 175 | const char *mapped; // Mapped file 176 | char *ptr, // Pointer into URL 177 | temp[1024], // Temporary path 178 | newurl[1024]; // New URL 179 | 180 | 181 | if (*url == '/') 182 | { 183 | if (!baseurl) 184 | return (hcPoolGetString(pool, url)); 185 | else if (!strncmp(baseurl, "http://", 7)) 186 | { 187 | strncpy(temp, baseurl, sizeof(temp) - 1); 188 | temp[sizeof(temp) - 1] = '\0'; 189 | if ((ptr = strchr(temp + 7, '/')) != NULL) 190 | *ptr = '\0'; 191 | 192 | snprintf(newurl, sizeof(newurl), "%s%s", temp, url); 193 | url = newurl; 194 | } 195 | else if (!strncmp(baseurl, "https://", 8)) 196 | { 197 | strncpy(temp, baseurl, sizeof(temp) - 1); 198 | temp[sizeof(temp) - 1] = '\0'; 199 | if ((ptr = strchr(temp + 8, '/')) != NULL) 200 | *ptr = '\0'; 201 | 202 | snprintf(newurl, sizeof(newurl), "%s%s", temp, url); 203 | url = newurl; 204 | } 205 | else 206 | return (hcPoolGetString(pool, url)); 207 | } 208 | else if (strncmp(url, "http://", 7) && strncmp(url, "https://", 8)) 209 | { 210 | if (!baseurl) 211 | { 212 | getcwd(temp, sizeof(temp)); 213 | snprintf(newurl, sizeof(newurl), "%s/%s", temp, url); 214 | 215 | return (hcPoolGetString(pool, newurl)); 216 | } 217 | else 218 | { 219 | strncpy(temp, baseurl, sizeof(temp) - 1); 220 | temp[sizeof(temp) - 1] = '\0'; 221 | 222 | if ((ptr = strrchr(temp, '/')) != NULL) 223 | *ptr = '\0'; 224 | 225 | snprintf(newurl, sizeof(newurl), "%s/%s", temp, url); 226 | 227 | if (newurl[0] == '/') 228 | return (hcPoolGetString(pool, newurl)); 229 | 230 | url = newurl; 231 | } 232 | } 233 | 234 | if ((mapped = (pool->url_cb)(pool->url_ctx, url, temp, sizeof(temp))) != NULL) 235 | { 236 | if (!pool->urls) 237 | pool->urls = hcDictNew(pool); 238 | 239 | hcDictSetKeyValue(pool->urls, url, temp); 240 | mapped = hcPoolGetString(pool, temp); 241 | } 242 | 243 | return (mapped); 244 | } 245 | 246 | 247 | // 248 | // 'hcPoolNew()' - Create a new memory pool. 249 | // 250 | 251 | hc_pool_t * // O - New memory pool 252 | hcPoolNew(void) 253 | { 254 | hc_pool_t *pool = (hc_pool_t *)calloc(1, sizeof(hc_pool_t)); 255 | // New memory pool 256 | 257 | if (pool) 258 | { 259 | if ((pool->loc = localeconv()) != NULL) 260 | { 261 | if (!pool->loc->decimal_point || !strcmp(pool->loc->decimal_point, ".")) 262 | pool->loc = NULL; 263 | else 264 | pool->loc_declen = strlen(pool->loc->decimal_point); 265 | } 266 | 267 | pool->error_cb = _hcDefaultErrorCB; 268 | pool->url_cb = _hcDefaultURLCB; 269 | } 270 | 271 | return (pool); 272 | } 273 | 274 | 275 | // 276 | // 'hcPoolSetErrorCallback()' - Set the error reporting callback. 277 | // 278 | // The default error callback writes the message to `stderr`. 279 | // 280 | // The error callback returns 1 to continue processing or 0 to stop immediately. 281 | // 282 | 283 | void 284 | hcPoolSetErrorCallback( 285 | hc_pool_t *pool, // I - Memory pool 286 | hc_error_cb_t cb, // I - Error callback or `NULL` for the default 287 | void *ctx) // I - Context pointer for callback 288 | { 289 | if (!pool) 290 | return; 291 | 292 | pool->error_cb = cb ? cb : _hcDefaultErrorCB; 293 | pool->error_ctx = ctx; 294 | } 295 | 296 | 297 | // 298 | // 'hcPoolSetURLCallback()' - Set the URL callback. 299 | // 300 | // The default URL callback supports local files (only). 301 | // 302 | // The URL callback returns a local pathname (copied to the specified buffer) 303 | // or `NULL` if the URL cannot be loaded/found. 304 | // 305 | 306 | void 307 | hcPoolSetURLCallback( 308 | hc_pool_t *pool, // I - Memory pool 309 | hc_url_cb_t cb, // I - URL callback or `NULL` for the default 310 | void *ctx) // I - Context pointer for callback 311 | { 312 | if (!pool) 313 | return; 314 | 315 | pool->url_cb = cb ? cb : _hcDefaultURLCB; 316 | pool->url_ctx = ctx; 317 | } 318 | 319 | 320 | // 321 | // 'compare_strings()' - Compare two strings... 322 | // 323 | 324 | static int // O - Result of comparison 325 | compare_strings(char **a, // I - First string 326 | char **b) // I - Second string 327 | { 328 | return (strcmp(*a, *b)); 329 | } 330 | -------------------------------------------------------------------------------- /pool.h: -------------------------------------------------------------------------------- 1 | // 2 | // Memory pool header for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #ifndef HTMLCSS_POOL_H 13 | # define HTMLCSS_POOL_H 14 | # include "common.h" 15 | # ifdef __cplusplus 16 | extern "C" { 17 | # endif // __cplusplus 18 | 19 | 20 | // 21 | // Types... 22 | // 23 | 24 | typedef struct _hc_pool_s hc_pool_t; // Memory allocation pool 25 | 26 | typedef bool (*hc_error_cb_t)(void *ctx, const char *message, int linenum); 27 | typedef char *(*hc_url_cb_t)(void *ctx, const char *url, char *buffer, size_t bufsize); 28 | 29 | 30 | // 31 | // Functions... 32 | // 33 | 34 | extern void hcPoolDelete(hc_pool_t *pool); 35 | extern const char *hcPoolGetLastError(hc_pool_t *pool); 36 | extern const char *hcPoolGetString(hc_pool_t *pool, const char *s); 37 | extern const char *hcPoolGetURL(hc_pool_t *pool, const char *url, const char *baseurl); 38 | extern hc_pool_t *hcPoolNew(void); 39 | extern void hcPoolSetErrorCallback(hc_pool_t *pool, hc_error_cb_t cb, void *ctx); 40 | extern void hcPoolSetURLCallback(hc_pool_t *pool, hc_url_cb_t cb, void *ctx); 41 | 42 | 43 | # ifdef __cplusplus 44 | } 45 | # endif // __cplusplus 46 | #endif // !HTMLCSS_POOL_H 47 | -------------------------------------------------------------------------------- /run.h: -------------------------------------------------------------------------------- 1 | // 2 | // Block run header file for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2018-2025 by Michael R Sweet. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | 12 | #ifndef HTMLCSS_RUN_H 13 | # define HTMLCSS_RUN_H 14 | # include "html.h" 15 | # include "css.h" 16 | # ifdef __cplusplus 17 | extern "C" { 18 | # endif // __cplusplus 19 | 20 | 21 | // 22 | // Types... 23 | // 24 | 25 | 26 | // 27 | // Functions... 28 | // 29 | 30 | 31 | # ifdef __cplusplus 32 | } 33 | # endif // __cplusplus 34 | #endif // !HTMLCSS_RUN_H 35 | -------------------------------------------------------------------------------- /sha3.c: -------------------------------------------------------------------------------- 1 | // 2 | // SHA3 hash implementation for HTMLCSS library. 3 | // 4 | // https://github.com/michaelrsweet/htmlcss 5 | // 6 | // Copyright © 2015-2025 Michael R Sweet. All rights reserved. 7 | // 8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more 9 | // information. 10 | // 11 | // The following code is adapted from the 12 | // "Keccak-readable-and-compact.c" source code from the official 13 | // Keccak reference implementation code repository: 14 | // 15 | // https://github.com/gvanas/KeccakCodePackage.git 16 | // 17 | // The original code bore the following notice: 18 | // 19 | // Implementation by the Keccak, Keyak and Ketje Teams, namely, 20 | // Guido Bertoni, Joan Daemen, Michaël Peeters, Gilles Van Assche 21 | // and Ronny Van Keer, hereby denoted as "the implementer". 22 | // 23 | // For more information, feedback or questions, please refer to 24 | // our websites: 25 | // 26 | // http://keccak.noekeon.org/ 27 | // http://keyak.noekeon.org/ 28 | // http://ketje.noekeon.org/ 29 | // 30 | // To the extent possible under law, the implementer has waived 31 | // all copyright and related or neighboring rights to the source 32 | // code in this file. 33 | // 34 | // http://creativecommons.org/publicdomain/zero/1.0/ 35 | // 36 | 37 | #include "common-private.h" 38 | #include 39 | #include "sha3.h" 40 | 41 | 42 | // 43 | // Endian tests to get LITTLE_ENDIAN defined to 1 on little-endian 44 | // systems; force it with "-DLITTLE_ENDIAN=1" to get the "optimized" 45 | // implementation. 46 | // 47 | 48 | #ifndef LITTLE_ENDIAN 49 | # ifdef _WIN32 50 | # define LITTLE_ENDIAN 1 // Windows is always little-endian 51 | # elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 52 | # define LITTLE_ENDIAN 1 // GCC/Clang preprocessor check 53 | # else 54 | # define LITTLE_ENDIAN 0 // Otherwise don't assume little-endian 55 | # endif // _WIN32 56 | #endif // !LITTLE_ENDIAN 57 | 58 | 59 | // 60 | // Local types... 61 | // 62 | 63 | typedef uint64_t tKeccakLane; // @private@ 64 | 65 | 66 | // 67 | // Local functions... 68 | // 69 | 70 | static void KeccakF1600_StatePermute(void *state); 71 | 72 | 73 | // 74 | // 'hcSHA3Final()' - Finalize the SHA3-256 hash, putting the result in "hash". 75 | // 76 | 77 | void 78 | hcSHA3Final(hc_sha3_t *ctx, // I - Hash context 79 | unsigned char *hash, // I - Hash buffer 80 | size_t hashlen) // I - Bytes to copy from hash 81 | { 82 | ctx->state[ctx->used] ^= 0x06; 83 | ctx->state[ctx->block - 1] ^= 0x80; 84 | 85 | KeccakF1600_StatePermute(ctx->state); 86 | 87 | if (hashlen < sizeof(ctx->state)) 88 | { 89 | memcpy(hash, ctx->state, hashlen); 90 | } 91 | else 92 | { 93 | memcpy(hash, ctx->state, sizeof(ctx->state)); 94 | memset(hash + sizeof(ctx->state), 0, hashlen - sizeof(ctx->state)); 95 | } 96 | } 97 | 98 | 99 | // 100 | // 'hcSHA3Init()' - Initialize a SHA3-256 hashing context. 101 | // 102 | 103 | void 104 | hcSHA3Init(hc_sha3_t *ctx) // I - Hash context 105 | { 106 | memset(ctx, 0, sizeof(hc_sha3_t)); 107 | ctx->block = 72; 108 | } 109 | 110 | 111 | // 112 | // 'hcSHA3Update()' - Update the SHA3-256 hashing context with the given data. 113 | // 114 | 115 | void 116 | hcSHA3Update(hc_sha3_t *ctx, // I - Hash context 117 | const void *data, // I - Data to hash 118 | size_t datalen) // I - Number of bytes of data 119 | { 120 | const unsigned char *dataptr; // Pointer into data 121 | 122 | 123 | dataptr = (const unsigned char *)data; 124 | 125 | while (datalen > 0) 126 | { 127 | while (ctx->used < ctx->block && datalen > 0) 128 | { 129 | ctx->state[ctx->used++] ^= *dataptr++; 130 | datalen --; 131 | } 132 | 133 | if (ctx->used == ctx->block) 134 | { 135 | KeccakF1600_StatePermute(ctx->state); 136 | ctx->used = 0; 137 | } 138 | } 139 | } 140 | 141 | 142 | 143 | #if !LITTLE_ENDIAN 144 | // 145 | // 'load64()' - Load a 64-bit value using the little-endian (LE) convention. 146 | // 147 | // On a LE platform, this can be greatly simplified using a cast. 148 | // 149 | 150 | static uint64_t // O - 64-bit integer value 151 | load64(const uint8_t *x) // I - Value buffer 152 | { 153 | int i; // Looping var 154 | uint64_t u = 0; // 64-bit value 155 | 156 | 157 | for (i = 7; i >= 0; i --) 158 | { 159 | u <<= 8; 160 | u |= x[i]; 161 | } 162 | 163 | return u; 164 | } 165 | 166 | // 167 | // 'store64()' - Store a 64-bit value using the little-endian (LE) convention. 168 | // 169 | // On a LE platform, this can be greatly simplified using a cast. 170 | // 171 | 172 | static void 173 | store64(uint8_t *x, // I - Value buffer 174 | uint64_t u) // I - 64-bit integer value to store 175 | { 176 | unsigned int i; // Looping var 177 | 178 | 179 | for (i = 0; i < 8; i ++) 180 | { 181 | x[i] = u; 182 | u >>= 8; 183 | } 184 | } 185 | 186 | 187 | // 188 | // 'xor64()' - XOR into a 64-bit value using the little-endian (LE) convention. 189 | // 190 | // On a LE platform, this can be greatly simplified using a cast. 191 | // 192 | 193 | static void 194 | xor64(uint8_t *x, // I - Value buffer 195 | uint64_t u) // I - 64-bit integer value to XOR 196 | { 197 | unsigned int i; // Looping var 198 | 199 | for (i = 0; i < 8; i ++) 200 | { 201 | x[i] ^= u; 202 | u >>= 8; 203 | } 204 | } 205 | #endif // !LITTLE_ENDIAN 206 | 207 | 208 | // 209 | // Helper macros... 210 | // 211 | 212 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 213 | #define ROL64(a, offset) ((((uint64_t)a) << offset) ^ (((uint64_t)a) >> (64-offset))) 214 | #define i(x, y) ((x)+5*(y)) 215 | 216 | #if LITTLE_ENDIAN 217 | # define readLane(x, y) (((tKeccakLane*)state)[i(x, y)]) 218 | # define writeLane(x, y, lane) (((tKeccakLane*)state)[i(x, y)]) = (lane) 219 | # define XORLane(x, y, lane) (((tKeccakLane*)state)[i(x, y)]) ^= (lane) 220 | #else 221 | # define readLane(x, y) load64((uint8_t*)state+sizeof(tKeccakLane)*i(x, y)) 222 | # define writeLane(x, y, lane) store64((uint8_t*)state+sizeof(tKeccakLane)*i(x, y), lane) 223 | # define XORLane(x, y, lane) xor64((uint8_t*)state+sizeof(tKeccakLane)*i(x, y), lane) 224 | #endif 225 | 226 | 227 | // 228 | // 'LFSR86540()' - Compute the linear feedback shift register (LFSR) used to 229 | // define the round constants (see [Keccak Reference, Section 230 | // 1.2]). 231 | // 232 | 233 | static int // O - Feedback result 234 | LFSR86540(uint8_t *LFSR) // I - Shift register 235 | { 236 | int result = ((*LFSR) & 0x01) != 0; // Feedback result 237 | 238 | 239 | if (((*LFSR) & 0x80) != 0) 240 | { 241 | // Primitive polynomial over GF(2): x^8+x^6+x^5+x^4+1 242 | (*LFSR) = (uint8_t)(((*LFSR) << 1) ^ 0x71); 243 | } 244 | else 245 | { 246 | (*LFSR) <<= 1; 247 | } 248 | 249 | return (result); 250 | } 251 | 252 | 253 | // 254 | // 'KeccakF1600_StatePermute()' - Compute the Keccak-f[1600] permutation on the 255 | // given state. 256 | // 257 | 258 | static void 259 | KeccakF1600_StatePermute(void *state) // I - State 260 | { 261 | unsigned int round, x, y, j, t; // Looping var 262 | uint8_t LFSRstate = 0x01; // Shift register 263 | 264 | 265 | for (round = 0; round < 24; round ++) 266 | { 267 | { // === θ step (see [Keccak Reference, Section 2.3.2]) === 268 | tKeccakLane C[5], D; 269 | 270 | // Compute the parity of the columns 271 | for (x = 0; x < 5; x ++) 272 | C[x] = readLane(x, 0) ^ readLane(x, 1) ^ readLane(x, 2) ^ readLane(x, 3) ^ readLane(x, 4); 273 | 274 | for (x = 0; x < 5; x ++) 275 | { 276 | // Compute the θ effect for a given column 277 | D = C[(x+4)%5] ^ ROL64(C[(x+1)%5], 1); 278 | 279 | // Add the θ effect to the whole column 280 | for (y=0; y<5; y++) 281 | XORLane(x, y, D); 282 | } 283 | } 284 | 285 | { // === ρ and π steps (see [Keccak Reference, Sections 2.3.3 and 2.3.4]) === 286 | tKeccakLane current, temp; 287 | 288 | // Start at coordinates (1 0) 289 | x = 1; y = 0; 290 | current = readLane(x, y); 291 | 292 | // Iterate over ((0 1)(2 3))^t * (1 0) for 0 ≤ t ≤ 23 293 | for (t = 0; t < 24; t ++) 294 | { 295 | // Compute the rotation constant r = (t+1)(t+2)/2 296 | unsigned int r = ((t+1)*(t+2)/2)%64; 297 | 298 | // Compute ((0 1)(2 3)) * (x y) 299 | unsigned int Y = (2*x+3*y)%5; x = y; y = Y; 300 | 301 | // Swap current and state(x,y), and rotate 302 | temp = readLane(x, y); 303 | writeLane(x, y, ROL64(current, r)); 304 | current = temp; 305 | } 306 | } 307 | 308 | { // === χ step (see [Keccak Reference, Section 2.3.1]) === 309 | tKeccakLane temp[5]; 310 | 311 | for (y = 0; y < 5; y ++) 312 | { 313 | // Take a copy of the plane 314 | for (x = 0; x < 5; x ++) 315 | temp[x] = readLane(x, y); 316 | 317 | // Compute χ on the plane 318 | for (x = 0; x < 5; x ++) 319 | writeLane(x, y, temp[x] ^((~temp[(x+1)%5]) & temp[(x+2)%5])); 320 | } 321 | } 322 | 323 | { // === ι step (see [Keccak Reference, Section 2.3.5]) === 324 | for (j = 0; j < 7; j ++) 325 | { 326 | unsigned int bitPosition = (1< 15 | # ifdef __cplusplus 16 | extern "C" { 17 | # endif // __cplusplus 18 | 19 | 20 | // 21 | // Constants... 22 | // 23 | 24 | # define HC_SHA3_256_SIZE 32 // SHA3-256 25 | # define HC_SHA3_512_SIZE 64 // SHA3-512 26 | 27 | 28 | // 29 | // Types... 30 | // 31 | 32 | typedef struct hc_sha3_s // SHA3 hashing context 33 | { 34 | unsigned char used, // Bytes "used" in state 35 | block, // Bytes per block 36 | state[200]; // SHA3 state 37 | } hc_sha3_t; 38 | 39 | typedef unsigned char hc_sha3_256_t[HC_SHA3_256_SIZE]; 40 | typedef unsigned char hc_sha3_512_t[HC_SHA3_512_SIZE]; 41 | 42 | 43 | // 44 | // Prototypes... 45 | // 46 | 47 | extern void hcSHA3Final(hc_sha3_t *ctx, unsigned char *hash, size_t hashlen); 48 | extern void hcSHA3Init(hc_sha3_t *ctx); 49 | extern void hcSHA3Update(hc_sha3_t *ctx, const void *data, size_t datalen); 50 | 51 | 52 | # ifdef __cplusplus 53 | } 54 | # endif // __cplusplus 55 | #endif // !HTMLCSS_SHA3_H 56 | --------------------------------------------------------------------------------