├── .github
└── workflows
│ └── wheels.yml
├── .gitignore
├── .gitmodules
├── .vscode
├── c_cpp_properties.json
├── launch.json
├── settings.json
└── tasks.json
├── CMakeLists.txt
├── LICENSE
├── README.md
├── bootstrap.min.css
├── build-wheels.sh
├── demo-pit11
├── make-demo-html
├── pit11.py
├── pit11.xml
├── pit11.xsd
├── pit11.xsl
└── styl.xsl
├── demo.html
├── litehtml-dumper.py
├── litehtml-pango-png.py
├── litehtml-t-web_color.py
├── litehtml-wx-browser.py
├── litehtml-wx-png-paged.py
├── litehtml-wx-png.py
├── litehtml-wx-print.py
├── litehtmld.py
├── litehtmlt.html
├── litehtmlt.py
├── logme.py
├── mingw-cross.cmake
├── pyproject.toml
├── set_inner_html.cpp
├── setup.py
├── src
├── background.h
├── borders.h
├── cairo
│ ├── cairo_borders.cpp
│ ├── cairo_borders.h
│ ├── cairo_images_cache.h
│ ├── container_cairo.cpp
│ ├── container_cairo.h
│ ├── container_cairo_pango.cpp
│ └── container_cairo_pango.h
├── css_length.h
├── css_margins.h
├── css_offsets.h
├── css_position.h
├── css_properties.h
├── css_selector.h
├── document.h
├── document_container.h
├── element.h
├── flex_item.h
├── flex_line.h
├── font_description.h
├── formatting_context.h
├── html.h
├── html_tag.h
├── iterators.h
├── line_box.h
├── litehtml.h
├── litehtmlpy.cpp
├── litehtmlpy
│ ├── __init__.py
│ ├── litehtml.py
│ ├── litehtmlpango.py
│ └── litehtmlwx.py
├── master_css.h
├── media_query.h
├── num_cvt.h
├── os_types.h
├── render_block.h
├── render_block_context.h
├── render_flex.h
├── render_image.h
├── render_inline.h
├── render_inline_context.h
├── render_item.h
├── render_table.h
├── string_id.h
├── style.h
├── stylesheet.h
├── table.h
├── tstring_view.h
├── types.h
└── web_color.h
├── test-01.html
├── test-02.html
├── weasy.py
├── x-build-cmake
├── x-build-cmake-mingw
├── x-build-docker
├── x-build-python
├── x-cleanup
├── x-debug-gdb
├── x-debug-valgrind
├── x-submodule
├── x-submodule-pull
└── y-bug-height.html
/.github/workflows/wheels.yml:
--------------------------------------------------------------------------------
1 | name: Wheels
2 |
3 | on:
4 | workflow_dispatch:
5 | # allow manual runs on branches without a PR
6 |
7 | jobs:
8 | build_ubuntu_wheels:
9 | name: Build wheels on Ubuntu
10 | runs-on: ubuntu-22.04
11 |
12 | timeout-minutes: 180
13 |
14 | steps:
15 | - name: Checkout sources
16 | uses: actions/checkout@v4
17 | with:
18 | submodules: true
19 |
20 | - name: Build wheels
21 | uses: pypa/cibuildwheel@v2.16
22 | env:
23 | CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
24 | CIBW_ARCHS_LINUX: x86_64
25 | CIBW_BUILD: "cp311*-manylinux_x86_64*"
26 |
27 | - name: Upload wheels
28 | uses: actions/upload-artifact@v4.3.0
29 | with:
30 | name: wheels_ubuntu__${{ github.sha }}
31 | path: wheelhouse/*.whl
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | todo/
2 |
3 | _deps/
4 | bin/
5 | CMakeFiles/
6 | lib/
7 |
8 | cmake_install.cmake
9 | CMakeCache.txt
10 | Makefile
11 | litehtmlpy/litehtmlpy.*.so
12 | src/litehtmlpy/litehtmlpy.*.so
13 | src/litehtmlpy/litehtmlpy.pyd
14 | .ninja_deps
15 | .ninja_log
16 | build.ninja
17 | litehtmlpy.egg-info/
18 |
19 | build/
20 | dist/
21 | wheelhouse/
22 | __pycache__/
23 | demo-pit11/xml/
24 |
25 | demo.png
26 | demo-*.html
27 | demo-*.png
28 |
29 | litehtmlpy.cpp
30 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "pybind11"]
2 | path = pybind11
3 | url = https://github.com/pybind/pybind11
4 | branch = v2.13
5 | [submodule "litehtml"]
6 | path = litehtml
7 | url = https://github.com/m32/litehtml
8 | branch = master
9 |
--------------------------------------------------------------------------------
/.vscode/c_cpp_properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "name": "Linux",
5 | "includePath": [
6 | "${workspaceRoot}",
7 | "${workspaceRoot}/litehtml/include",
8 | "/usr/include",
9 | "."
10 | ],
11 | "defines": [
12 | "_FILE_OFFSET_BITS=64",
13 | ],
14 | "browse": {
15 | "limitSymbolsToIncludedHeaders": true,
16 | "databaseFilename": "",
17 | "path": [
18 | "/usr/include"
19 | ]
20 | },
21 | "intelliSenseMode": "clang-x64",
22 | "compilerPath": "/usr/bin/clang",
23 | "cStandard": "c11",
24 | "cppStandard": "c++17",
25 | "configurationProvider": "ms-vscode.cmake-tools"
26 | }
27 | ],
28 | "version": 4
29 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "gdb",
6 | "type": "cppdbg",
7 | "request": "launch",
8 | "program": "/devel/bin/python3/bin/python",
9 | "stopAtEntry": true,
10 | "cwd": "${workspaceFolder}",
11 | "args": ["litehtml-pango-png.py"],
12 | "environment": [],
13 | "externalConsole": false,
14 | "MIMode": "gdb",
15 | "setupCommands": [
16 | {
17 | "description": "Enable pretty-printing for gdb",
18 | "text": "-enable-pretty-printing",
19 | "ignoreFailures": true
20 | }
21 | ]
22 | },
23 | {
24 | "name": "Python: Remote Attach",
25 | "type": "python",
26 | "request": "attach",
27 | "port": 10001,
28 | "host": "localhost",
29 | "pathMappings": [
30 | {
31 | "localRoot": "${workspaceFolder}",
32 | "remoteRoot": "/app"
33 | }
34 | ]
35 | },
36 | {
37 | "name": "Python: Current File",
38 | "type": "python",
39 | "request": "launch",
40 | "justMyCode": false,
41 | "cwd": "${workspaceFolder}",
42 | "program": "${file}"
43 | },
44 | {
45 | "name": "Python C++ Debug",
46 | "type": "pythoncpp",
47 | "request": "launch",
48 | "pythonConfig": "default",
49 | "cppConfig": "default (gdb) Attach",
50 | }
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.associations": {}
3 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "cmake",
8 | "type": "shell",
9 | "command": "cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DLITEHTML_BUILD_TESTING=OFF -DLITEHTMLPY_BUILD_TESTING=ON -DLITEHTMLPY_INCLUDE_CAIRO_CONTAINERS=ON -Wno-dev ."
10 | },
11 | {
12 | "label": "make",
13 | "type": "shell",
14 | "command": "make",
15 | "group": {
16 | "kind": "build",
17 | "isDefault": true
18 | },
19 | "problemMatcher": [
20 | "$gcc"
21 | ]
22 | }
23 | ]
24 | }
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #
2 | # cmake -DCMAKE_BUILD_TYPE=Debug .
3 | # cmake -DCMAKE_BUILD_TYPE=Release .
4 | #
5 | cmake_minimum_required(VERSION 3.4...3.18)
6 | project(litehtmlpy)
7 |
8 | # https://stackoverflow.com/questions/38296756/what-is-the-idiomatic-way-in-cmake-to-add-the-fpic-compiler-option
9 | set(CMAKE_POSITION_INDEPENDENT_CODE ON)
10 |
11 | option(LITEHTMLPY_BUILD_TESTING "enable testing for litehtmlpy" OFF)
12 | # option(LITEHTMLPY_INCLUDE_CAIRO_CONTAINERS "include cairo containers" OFF)
13 |
14 | #find_package(Git)
15 | #
16 | #if(NOT EXISTS ${CMAKE_SOURCE_DIR}/litehtml)
17 | # execute_process(COMMAND "${GIT_EXECUTABLE}" clone --depth 1 https://github.com/m32/litehtml -b master
18 | # WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
19 | #endif()
20 | #if(NOT EXISTS ${CMAKE_SOURCE_DIR}/pybind11)
21 | # if(CMAKE_CROSSCOMPILING)
22 | # execute_process(COMMAND "${GIT_EXECUTABLE}" clone --depth 1 https://github.com/pybind/pybind11.git -b v2.9
23 | # WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
24 | # else()
25 | # execute_process(COMMAND "${GIT_EXECUTABLE}" clone --depth 1 https://github.com/pybind/pybind11.git -b v2.11
26 | # WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
27 | # endif()
28 | #endif()
29 |
30 | add_subdirectory(litehtml)
31 |
32 | if(CMAKE_CROSSCOMPILING)
33 | # Tell pybind11 where the target python installation is
34 | set(PYTHON_INCLUDE_DIR ${MY_TARGET_PYTHON_INCLUDE_DIRS} CACHE INTERNAL "Cross python include path")
35 | set(PYTHON_INCLUDE_DIRS ${MY_TARGET_PYTHON_INCLUDE_DIRS} CACHE INTERNAL "Cross python include path")
36 | set(PYTHON_MODULE_EXTENSION ".pyd" CACHE INTERNAL "Cross python lib extension")
37 |
38 | # Disable pybind11 python search mechanism
39 | set(PYTHONLIBS_FOUND TRUE CACHE INTERNAL "")
40 |
41 | target_compile_definitions(litehtml PRIVATE
42 | -DLITEHTML_NO_THREADS
43 | )
44 | endif()
45 |
46 | add_subdirectory(pybind11)
47 |
48 | if(DEFINED LITEHTMLPY_INCLUDE_CAIRO_CONTAINERS AND LITEHTMLPY_INCLUDE_CAIRO_CONTAINERS)
49 | set(TEST_LITEHTMLPY_CONTAINERS
50 | src/cairo/cairo_borders.cpp
51 | src/cairo/container_cairo.cpp
52 | src/cairo/container_cairo_pango.cpp
53 | )
54 | else()
55 | set(TEST_LITEHTMLPY_CONTAINERS
56 | )
57 | endif()
58 |
59 |
60 | pybind11_add_module(
61 | litehtmlpy
62 | src/litehtmlpy.cpp
63 | ${TEST_LITEHTMLPY_CONTAINERS}
64 | )
65 |
66 | set_target_properties(litehtmlpy PROPERTIES
67 | LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/src/litehtmlpy")
68 |
69 | #if(NOT CMAKE_CROSSCOMPILING)
70 | # find_package(PythonLibs 3.6)
71 | # #find_package(PythonLibs 3.8 REQUIRED)
72 | # #find_package(PythonLibs 3.11.2 EXACT REQUIRED)
73 | #endif()
74 |
75 | set_target_properties(litehtmlpy PROPERTIES
76 | CXX_STANDARD 17
77 | C_STANDARD 99
78 | )
79 |
80 | target_compile_definitions(litehtmlpy PRIVATE
81 | #-DUNICODE
82 | )
83 |
84 | target_include_directories(litehtmlpy PRIVATE
85 | # ${PYTHON_INCLUDE_DIRS}
86 | litehtml/include
87 | # ${PYBIND11_INCLUDE_DIR}
88 | )
89 |
90 | target_link_libraries(litehtmlpy PRIVATE
91 | litehtml
92 | gumbo
93 | # ${PYTHON_LIBRARIES}
94 | )
95 |
96 | if(DEFINED LITEHTMLPY_INCLUDE_CAIRO_CONTAINERS AND LITEHTMLPY_INCLUDE_CAIRO_CONTAINERS)
97 | find_package(PkgConfig REQUIRED)
98 | pkg_check_modules(CAIRO REQUIRED IMPORTED_TARGET "cairo")
99 | target_link_libraries(litehtmlpy PRIVATE PkgConfig::CAIRO)
100 | pkg_check_modules(PANGO REQUIRED IMPORTED_TARGET "pango")
101 | target_link_libraries(litehtmlpy PRIVATE PkgConfig::PANGO)
102 | pkg_check_modules(PANGOCAIRO REQUIRED IMPORTED_TARGET "pangocairo")
103 | target_link_libraries(litehtmlpy PRIVATE PkgConfig::PANGOCAIRO)
104 | target_compile_definitions(litehtmlpy PRIVATE
105 | -DUSE_CAIRO_CONTAINERS
106 | )
107 | endif()
108 |
109 | if(CMAKE_CROSSCOMPILING)
110 | target_compile_definitions(litehtmlpy PRIVATE
111 | -DMS_WIN64
112 | )
113 | target_compile_options(litehtmlpy PRIVATE
114 | -Wa,-mbig-obj
115 | )
116 | target_link_options(litehtmlpy PRIVATE
117 | -static
118 | -static-libgcc
119 | -static-libstdc++
120 | )
121 | endif()
122 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Grzegorz Makarewicz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # python-litehtml
2 | LiteHtmlPy is a solution that helps python developers to create printouts and previews of html5/css3 pages without using a web browser.
3 |
4 | ## Links
5 | * [litehtml library](https://github.com/litehtml/litehtml)
6 | * [pybind11](https://github.com/pybind/pybind11)
7 |
8 | ## Cleanup repository
9 | ./x-cleanup
10 |
11 | ## Linux configure
12 | - ./x-build-cmake -DCMAKE_BUILD_TYPE=Debug
13 | - ./x-build-cmake -DCMAKE_BUILD_TYPE=Release
14 |
15 | ## Cross Mingw configure for python 2.7
16 | - ./x-build-cmake-mingw
17 |
18 | ## Build for manylinux inside docker
19 | - ./x-build-docker
20 |
21 | ## Just setup.py build
22 | - ./x-build-python
23 |
--------------------------------------------------------------------------------
/build-wheels.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e -u -x
3 | cd /io
4 |
5 | #[ -n "$WHEELHOUSE" ] ||
6 | WHEELHOUSE=wheelhouse
7 | #[ -z "$PYTHON_BUILD_VERSION" ] &&
8 | PYTHON_BUILD_VERSION="cp311-cp311"
9 |
10 | function repair_wheel {
11 | wheel="$1"
12 | if ! auditwheel show "$wheel"; then
13 | echo "Skipping non-platform wheel $wheel"
14 | else
15 | auditwheel repair "$wheel" -w /io/wheelhouse/
16 | fi
17 | }
18 |
19 |
20 | # Install a system package required by our library
21 | # yum install -y pybind11
22 |
23 | # Compile wheels
24 | for PYBIN in /opt/python/${PYTHON_BUILD_VERSION}/bin; do
25 | #"${PYBIN}/pip" install -r /io/dev-requirements.txt
26 | "${PYBIN}/pip" wheel /io/ --no-deps -w /io/${WHEELHOUSE} || exit 1
27 | done
28 |
29 | # Bundle external shared libraries into the wheels
30 | for whl in wheelhouse/*.whl; do
31 | repair_wheel "$whl"
32 | done
33 |
34 | # Install packages and test
35 | for PYBIN in /opt/python/${PYTHON_BUILD_VERSION}/bin; do
36 | "${PYBIN}/pip" install litehtmlpy --no-index -f /io/${WHEELHOUSE}
37 | "${PYBIN}/python" litehtmld.py
38 | done
39 |
--------------------------------------------------------------------------------
/demo-pit11/make-demo-html:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ./pit11.py --xml=pit11.xml --xsl=pit11.xsl --html=../demo.html
3 |
--------------------------------------------------------------------------------
/demo-pit11/pit11.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env vpython3
2 | # -*- coding: utf-8 -*-
3 | import os
4 | import sys
5 | import getopt
6 | import urllib.request
7 | import base64
8 | import zipfile
9 | import io
10 | from lxml import etree
11 |
12 | top = os.getcwd()
13 |
14 | def download(url):
15 | # http://crd.gov.pl/wzor/2014/12/22/1949/schemat.xsd
16 | if url[:7] == 'http://':
17 | fname = url.split('/')
18 | assert fname[2] in ('crd.gov.pl', 'jpk.mf.gov.pl')
19 | fname = '/'.join(fname[3:])
20 | filepath = '/'.join((top, fname))
21 | else:
22 | filepath = url
23 | # print('download', url, filepath)
24 | if not os.path.isfile(filepath):
25 | dirname = '/'.join(filepath.split('/')[:-1])
26 | if not os.path.exists(dirname):
27 | os.makedirs(dirname)
28 | print("Resolving URL '%s'" % url)
29 | response = urllib.request.urlopen(url)
30 | data = response.read()
31 | open(filepath, 'wb').write(data)
32 | return filepath
33 |
34 | class DTDResolver(etree.Resolver):
35 | def resolve(self, url, id, context):
36 | filepath = download(url)
37 | return self.resolve_filename(filepath, context)
38 |
39 | class Parser:
40 | namespaces={
41 | # 'dsig' :"http://www.w3.org/2000/09/xmldsig#",
42 | # 'xsd' :"http://www.w3.org/2001/XMLSchema",
43 | # 'soap' :"http://www.w3.org/2003/05/soap-envelope",
44 | }
45 |
46 | def __init__(self, xml):
47 | self.xml = xml
48 | self.tree = etree.parse(io.BytesIO(xml))
49 | self.namespaces.update(self.tree.getroot().nsmap)
50 |
51 | class Deklaracja:
52 | def __init__(self, xml):
53 | self.xml = xml
54 | self.xsd = None
55 | self.xsl = None
56 | self.url = None
57 |
58 | self.xsdformularz = None
59 | self.xsdwariant = None
60 | self.xsdwersja = None
61 | self.firmanip = None
62 | self.firmanazwa = None
63 |
64 | self.prepare()
65 |
66 | def prepare(self):
67 | self.tree = etree.parse(io.BytesIO(self.xml))
68 | self.root = self.tree.getroot()
69 | self.nsmap = self.root.nsmap.copy()
70 | try:
71 | self.url = self.root.nsmap[None]
72 | self.nsmap['doc'] = self.nsmap[None]
73 | del self.nsmap[None]
74 | except:
75 | pass
76 |
77 | def getxsd(self):
78 | if self.xsd:
79 | return self.xsd
80 | return download(self.url+'schemat.xsd')
81 |
82 | def getxsl(self):
83 | if self.xsl:
84 | return self.xsl
85 | return download(self.url+'styl.xsl')
86 |
87 | def validate(self):
88 | fxsd = self.getxsd()
89 | print('fxsd=', fxsd)
90 | xsdparser = etree.XMLParser(load_dtd=True)
91 | xsdparser.resolvers.add( DTDResolver() )
92 |
93 | schema_root = etree.XML(open(fxsd, 'rb').read(), xsdparser)
94 | schema = etree.XMLSchema(schema_root)
95 |
96 | xmlparser = etree.XMLParser(schema = schema)
97 | etree.clear_error_log()
98 | try:
99 | oot = etree.parse(io.BytesIO(self.xml), xmlparser)
100 | except etree.XMLSyntaxError as e:
101 | return e
102 | return None
103 |
104 | def showerror(self, e):
105 | for ee in e.error_log:
106 | print(ee.line, ee.column, ee.message)
107 |
108 | def html(self):
109 | fxsl = self.getxsl()
110 |
111 | xsdparser = etree.XMLParser(load_dtd=True)
112 | xsdparser.resolvers.add( DTDResolver() )
113 |
114 | xslt_root = etree.XML(open(fxsl, 'rb').read(), xsdparser)
115 | transform = etree.XSLT(xslt_root)
116 |
117 | result = transform(self.tree)
118 | result = etree.tostring(result)
119 | return result
120 |
121 | def info(self):
122 | self.info_naglowek()
123 | self.info_osoba()
124 |
125 | def info_naglowek(self):
126 | naglowek = '/doc:Deklaracja/doc:Naglowek'
127 | formularz = self.tree.xpath( naglowek+'/doc:KodFormularza', namespaces=self.nsmap )[0]
128 | wariant = self.tree.xpath( naglowek+'/doc:WariantFormularza', namespaces=self.nsmap )[0].text
129 |
130 | self.xsdformularz = formularz.text
131 | self.xsdwariant = wariant
132 | self.xsdwersja = formularz.get('wersjaSchemy')
133 |
134 | def info_osoba(self):
135 | path = '/doc:Deklaracja/doc:Podmiot1/etd:OsobaFizyczna'
136 | podmiot = self.tree.xpath( path, namespaces=self.nsmap )
137 | if podmiot:
138 | self.firmanip = self.tree.xpath( path+'/etd:NIP', namespaces=self.nsmap )[0].text
139 | imie = self.tree.xpath( path+'/etd:ImiePierwsze', namespaces=self.nsmap )[0].text
140 | nazwisko = self.tree.xpath( path+'/etd:Nazwisko', namespaces=self.nsmap )[0].text
141 | self.firmanazwa = imie+' '+nazwisko
142 | else:
143 | path = '/doc:Deklaracja/doc:Podmiot1/etd:OsobaNiefizyczna'
144 | podmiot = self.tree.xpath( path, namespaces=self.nsmap )
145 | self.firmanip = self.tree.xpath( path+'/etd:NIP', namespaces=self.nsmap )[0].text
146 | self.firmanazwa = self.tree.xpath( path+'/etd:PelnaNazwa', namespaces=self.nsmap )[0].text
147 |
148 | class XADESParser(Parser):
149 | def __init__(self, xml):
150 | Parser.__init__(self, xml)
151 | self.dokument = None
152 |
153 | ns = [
154 | '/ds:Signature',
155 | '/ds:SignedInfo',
156 | '/ds:Reference',
157 | ]
158 | path = ''.join(ns)
159 | odpowiedz = self.tree.xpath( path, namespaces=self.namespaces )
160 | odpowiedz = odpowiedz[0]
161 | ref = odpowiedz.get('URI')[1:]
162 | ns = [
163 | '/ds:Signature',
164 | '/ds:Object',
165 | ]
166 | path = ''.join(ns)
167 | print(ref)
168 | for odpowiedz in self.tree.xpath( path, namespaces=self.namespaces ):
169 | print(odpowiedz.get('Id'))
170 | if ref == odpowiedz.get('Id'):
171 | print(dir(odpowiedz))
172 | dokument = odpowiedz.text
173 | print('dokument', dokument)
174 | dokument = base64.decodestring(dokument)
175 | fp = io.BytesIO(dokument)
176 | zfp = zipfile.ZipFile(fp, 'r', zipfile.ZIP_DEFLATED)
177 | self.dokument = zfp.read(zfp.namelist()[0])
178 | return
179 |
180 | def main():
181 | opts, args = getopt.getopt(sys.argv[1:], '?', [
182 | 'help',
183 | 'validate',
184 | 'xml=',
185 | 'xsd=',
186 | 'html=',
187 | 'xsl=',
188 | 'xades=',
189 | ])
190 | if args or '-?' in opts or '--help' in opts:
191 | print('''\
192 | usage: %s opcje
193 |
194 | opcje:
195 | --help = pokaz ten tekst
196 | -? = pokaz ten tekst
197 | --validate = sprawdz poprawnosc pliku (--xml|--xades)
198 | --xsd= = nazwa pliku xsd
199 | --html=plik = gdzie zapisac plik html
200 | --xsl= = nazwa pliku xsl
201 | --xades=plik = wczytaj plik xml z podpisanego pliku (xades)
202 | --xml=plik = wczytaj plik xml
203 | ''' % sys.argv[0])
204 | return
205 | dek = None
206 | for o, a in opts:
207 | if o == '--xml':
208 | xml=open(a, 'rb').read()
209 | dekok = Deklaracja(xml)
210 | elif o == '--xsd':
211 | dekok.xsd = a
212 | elif o == '--xsl':
213 | dekok.xsl = a
214 | elif o == '--xades':
215 | xml = open(a, 'rb').read()
216 | p = XADESParser(xml)
217 | dekok = Deklaracja(p.dokument)
218 | elif o == '--validate':
219 | print('*'*20, 'validate')
220 | e = dekok.validate()
221 | if e:
222 | dekok.showerror(e)
223 | else:
224 | print('OK')
225 | elif o == '--html':
226 | print('*'*20, 'html')
227 | lines = io.BytesIO(dekok.html()).readlines()
228 | html = []
229 | for line in lines:
230 | line = line.strip()
231 | if len(line) > 0:
232 | html.append(line)
233 | html = b'\n'.join(html)
234 | open(a, 'wb').write(html)
235 | print('OK')
236 |
237 | main()
238 |
--------------------------------------------------------------------------------
/demo-pit11/pit11.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 | PIT-11
9 | 29
10 | 1
11 | 2023
12 | 2205
13 |
14 |
15 |
16 | 1234567890
17 | Firma Sp. z o.o.
18 |
19 |
20 |
21 |
22 | 12345678901
23 | Imię
24 | Nazwisko
25 | 1234-56-78
26 |
27 |
28 | PL
29 | Województwo
30 | Powiat
31 | Gmina
32 | Ulica
33 | 1
34 | Miejscowość
35 | 12-345
36 |
37 |
38 |
39 | 1
40 | 1
41 | 249525.40
42 | 3000.00
43 | 246525.40
44 | 0.00
45 | 41878
46 | 0.00
47 | 0.00
48 | 0.00
49 | 0.00
50 | 0.00
51 | 0.00
52 | 0
53 | 0.00
54 | 0.00
55 | 0.00
56 | 0.00
57 | 0.00
58 | 0.00
59 | 0
60 | 0.00
61 | 0.00
62 | 0.00
63 | 0.00
64 | 0.00
65 | 0
66 | 0.00
67 | 0.00
68 | 0.00
69 | 0
70 | 0.00
71 | 0.00
72 | 0.00
73 | 0
74 | 0.00
75 | 0.00
76 | 0.00
77 | 0
78 | 0.00
79 | 0.00
80 | 0.00
81 | 0
82 | 0.00
83 | 0.00
84 | 0
85 | 0.00
86 | 0.00
87 | 0.00
88 | 0.00
89 | 0
90 | 0.00
91 | 0.00
92 | 0
93 | 0.00
94 | 0.00
95 | 0
96 | 0.00
97 | 0.00
98 | 0
99 | 0.00
100 | 0.00
101 | 0
102 | 0.00
103 | 0.00
104 | 0.00
105 | 0.00
106 | 0
107 | 29403.26
108 | 0.00
109 | 0.00
110 | 0.00
111 | 0.00
112 | 0.00
113 | 0.00
114 | 0.00
115 | 0.00
116 | 0.00
117 | 0.00
118 | 0.00
119 | 0.00
120 | 0.00
121 | 0.00
122 | 2
123 | 19309.38
124 | 0.00
125 |
126 | 1
127 |
128 |
--------------------------------------------------------------------------------
/litehtml-dumper.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env vpython3
2 | import sys
3 | import logging
4 |
5 | import logme
6 | from litehtmlpy import litehtml, litehtmlpy
7 |
8 | litehtmlpy.debuglog(0)
9 |
10 | logger = logging.getLogger(__name__)
11 |
12 | class document_container(litehtml.document_container):
13 | pass
14 |
15 | class dumper(litehtmlpy.dumper):
16 | def __init__(self):
17 | super().__init__()
18 | self.indent = 0
19 |
20 | def printi(self, *args):
21 | s = ' '*self.indent if self.indent else ''
22 | print(s, *args)
23 |
24 | def begin_node(self, descr):
25 | self.printi('begin_node', descr)
26 | self.indent += 1
27 |
28 | def end_node(self):
29 | self.indent -= 1
30 | self.printi('end_node')
31 |
32 | def begin_attrs_group(self, descr):
33 | self.printi('begin_attrs_group', descr)
34 | self.indent += 1
35 |
36 | def end_attrs_group(self):
37 | self.indent -= 1
38 | self.printi('end_attrs_group')
39 |
40 | def add_attr(self, name, value):
41 | self.printi('add_attr', name, value)
42 |
43 | class Main:
44 | def demo(self):
45 | if len(sys.argv) > 1:
46 | fname = sys.argv[1]
47 | else:
48 | fname = 'demo.html'
49 | fname = 'litehtmlt.html'
50 | html = open(fname, 'rt').read()
51 |
52 | cntr = document_container()
53 | print('max size=', cntr.size)
54 |
55 | doc = litehtmlpy.fromString(cntr, html, None, None)
56 | doc.render(cntr.size[0], litehtmlpy.render_all)
57 | print('doc: width:', doc.width(), 'height:', doc.height())
58 |
59 | dump = dumper()
60 | doc.dump(dump)
61 |
62 | del doc
63 | del cntr
64 |
65 | def main():
66 | app = Main()
67 | app.demo()
68 |
69 | main()
70 |
--------------------------------------------------------------------------------
/litehtml-pango-png.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env vpython3
2 | import gc
3 | import sys
4 | import os
5 | import io
6 | import logging
7 | import requests
8 | import urllib.parse
9 | from PIL import Image
10 | import logme
11 | from litehtmlpy import litehtmlpango, litehtmlpy
12 |
13 | litehtmlpy.debuglog(1)
14 |
15 | logger = logging.getLogger(__name__)
16 |
17 | class document_container(litehtmlpango.document_container):
18 | def import_css(self, text, url, base_url):
19 | url = urllib.parse.urljoin(base_url, url)
20 | if os.path.exists(url):
21 | with open(url, 'rt') as fp:
22 | data = fp.read()
23 | elif url.split(':')[0] in ('http', 'https'):
24 | r = requests.get(url)
25 | if r.headers['Content-Type'] == 'text/css':
26 | data = r.text
27 | if data is None:
28 | print('unknown import_css', url)
29 | return
30 | return data
31 |
32 | class App:
33 | images = {}
34 | url = ''
35 | def GetUrlData(self, url, html=True):
36 | data = None
37 | if os.path.exists(url):
38 | with open(url, 'rb') as fp:
39 | data = fp.read()
40 | elif url.split(':')[0] in ('http', 'https'):
41 | r = requests.get(url)
42 | print(r.headers)
43 | if html:
44 | if r.headers['Content-Type'] == 'text/html':
45 | data = r.text
46 | else:
47 | if r.headers['Content-Type'] == 'image/png':
48 | data = r.content
49 | if data is None:
50 | return None
51 | return data
52 |
53 | def HtmlLoadImage(self, cntr, src, baseurl, redraw_on_ready):
54 | if baseurl is not None:
55 | url = urllib.parse.urljoin(baseurl, src)
56 | else:
57 | url = urllib.parse.urljoin(self.url, src)
58 | data = self.GetUrlData(url, False)
59 | print('HtmlLoadImage({}, {})'.format(url, data is not None))
60 | if data is None:
61 | return
62 | try:
63 | im = Image.open(io.BytesIO(data))
64 | except:
65 | print('bang')
66 | return
67 | if 'A' not in im.getbands():
68 | alpha = 1.0
69 | im.putalpha(int(alpha * 256.))
70 | try:
71 | arr = bytearray(im.tobytes('raw', 'BGRa'))
72 | except ValueError:
73 | return
74 | cntr.put_image(url, arr, im.width, im.height)
75 |
76 |
77 | class Main:
78 | def demo(self):
79 | app = App()
80 | if len(sys.argv) > 1:
81 | fname = sys.argv[1]
82 | else:
83 | fname = 'demo.html'
84 | fname = 'litehtmlt.html'
85 | html = open(fname, 'rt').read()
86 |
87 | cntr = document_container(app)
88 | print('max size=', cntr.size)
89 |
90 | doc = cntr.fromString(html, None, None)
91 | doc.render(cntr.size[0], litehtmlpy.render_all)
92 | print('doc: width:', doc.width(), 'height:', doc.height())
93 |
94 | cntr.size[1] = doc.height()
95 | hdc = cntr.surface(doc.width(), doc.height())
96 |
97 | print('*'*10, 'draw')
98 | clip = litehtmlpy.position(0, 0, doc.width(), doc.height())
99 | doc.draw(hdc, 0, 0, clip)
100 |
101 | print('x-save')
102 | cntr.save('demo.png')
103 |
104 | print('x-save-stream')
105 | with open('demo-1.png', 'wb') as fpo:
106 | rc = cntr.savestream(fpo.write)
107 | print('save result:', rc)
108 |
109 | del doc
110 | cntr.clear_images()
111 | del cntr
112 |
113 | def main():
114 | app = Main()
115 | app.demo()
116 |
117 | main()
118 | gc.collect()
119 |
--------------------------------------------------------------------------------
/litehtml-t-web_color.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env vpython3
2 | import sys
3 | import logme
4 | from litehtmlpy import litehtml, litehtmlpy
5 |
6 | #litehtmlpy.debuglog(1)
7 |
8 | class document_container(litehtml.document_container):
9 | pass
10 |
11 | class Main:
12 | def main(self):
13 | if len(sys.argv) > 1:
14 | fname = sys.argv[1]
15 | else:
16 | fname = 'demo.html'
17 | fname = 'litehtmlt.html'
18 | html = open(fname, 'rt').read()
19 |
20 | cntr = document_container()
21 | try:
22 | self.demo(cntr)
23 | finally:
24 | del cntr
25 |
26 | def demo(self, cntr):
27 | wc00 = litehtmlpy.web_color()
28 | print(wc00)
29 | wcff = litehtmlpy.web_color(0xff, 0xff, 0xff)
30 | print(wcff)
31 | print(wc00==wcff)
32 | print(wc00!=wcff)
33 | print('black', litehtmlpy.web_color.black)
34 | print('white', litehtmlpy.web_color.white)
35 | print('transparent', litehtmlpy.web_color.transparent)
36 | print('current', litehtmlpy.web_color.current_color)
37 | wcffd = wcff.darken(0.5)
38 | print(wcffd)
39 |
40 | def main():
41 | app = Main()
42 | app.main()
43 |
44 | main()
45 |
--------------------------------------------------------------------------------
/litehtml-wx-png-paged.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env vpython3
2 | import logme
3 | import wx
4 | from litehtmlpy import litehtmlwx, litehtmlpy
5 |
6 | class document_container(litehtmlwx.document_container):
7 | pass
8 |
9 | class Main:
10 | def __init__(self):
11 | self.wxapp = wx.App(False)
12 |
13 | def save(self, i, htmlstart, htmldata, htmlend):
14 | html = htmlstart+htmldata+htmlend
15 | if 0:
16 | fp = open('demo-{i:04d}.html'.format(i=i), 'wt')
17 | fp.write(html)
18 | fp.close()
19 |
20 | cntr = document_container()
21 | cntr.reset()
22 |
23 | doc = litehtmlpy.fromString(cntr, html, None, None)
24 | doc.render(cntr.size[0], litehtmlpy.render_all)
25 |
26 | cntr.size[1] = doc.height()
27 | cntr.reset()
28 | clip = litehtmlpy.position(0, 0, doc.width(), doc.height())
29 | doc.draw(0, 0, 0, clip)
30 |
31 | cntr.bmp.SaveFile('demo-{i:04d}.png'.format(i=i), wx.BITMAP_TYPE_PNG)
32 |
33 | del doc
34 | del cntr
35 |
36 | def main(self):
37 | if len(sys.argv) > 1:
38 | fname = sys.argv[1]
39 | else:
40 | fname = 'demo.html'
41 | html = open(fname, 'rt').read()
42 | split='
'
43 | start = html.find(split) + len(split)
44 | start = html.find('>', start) + 1
45 | i = end = start
46 | while True:
47 | i = html.find('', i+1)
48 | if i == -1:
49 | break
50 | end = i
51 | htmlstart = html[0:start]
52 | htmldata = html[start:end]
53 | htmlend = html[end:]
54 | i = 0
55 | split = ''
56 | while True:
57 | istop = htmldata.find(split)
58 | if istop == -1:
59 | self.save(i, htmlstart, htmldata, htmlend)
60 | break
61 | self.save(i, htmlstart, htmldata[:istop], htmlend)
62 | htmldata = htmldata[istop+len(split):]
63 | i += 1
64 |
65 | def main():
66 | cls = Main()
67 | cls.main()
68 | main()
69 |
--------------------------------------------------------------------------------
/litehtml-wx-png.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env vpython3
2 | import sys
3 | import wx
4 |
5 | import logme
6 | from litehtmlpy import litehtmlwx, litehtmlpy
7 |
8 | class document_container(litehtmlwx.document_container):
9 | def pt_to_px(self, pt):
10 | return pt
11 |
12 | class Main:
13 | def __init__(self):
14 | self.wxapp = wx.App(False)
15 |
16 | def save(self, i, htmlstart, htmlend, htmldata):
17 | html = htmlstart+htmldata+htmlend
18 |
19 | dc = wx.MemoryDC()
20 |
21 | cntr = document_container()
22 | cntr.SetDC(dc)
23 | doc = litehtmlpy.fromString(cntr, html, None, None)
24 | doc.render(cntr.size[0], litehtmlpy.render_all)
25 |
26 | cntr.size[1] = doc.height()
27 |
28 | bmp = wx.Bitmap(cntr.size[0], cntr.size[1], 32)
29 | dc.SelectObject(bmp)
30 | dc.SetBackground(wx.Brush(wx.WHITE))
31 | dc.Clear()
32 |
33 | clip = litehtmlpy.position(0, 0, doc.width(), doc.height())
34 | doc.draw(0, 0, 0, clip)
35 |
36 | bmp.SaveFile('demo-{i:04d}.png'.format(i=i), wx.BITMAP_TYPE_PNG)
37 |
38 | cntr.SetDC(None)
39 | del doc
40 | del cntr
41 |
42 | def main(self):
43 | if len(sys.argv) > 1:
44 | fname = sys.argv[1]
45 | else:
46 | fname = 'demo.html'
47 | html = open(fname, 'rt').read()
48 | split=''
49 | start = html.find(split) + len(split)
50 | end = html.find('')
51 | htmlstart = html[0:start]
52 | htmlend = html[end:]
53 | html = html[start:end]
54 | htmlpages = []
55 | split = ''
56 | start = 0
57 | while True:
58 | stop = html.find(split, start)
59 | if stop == -1:
60 | htmlpages.append((start, len(html)))
61 | break
62 | htmlpages.append((start, stop))
63 | start = stop+len(split)
64 | for i in range(len(htmlpages)):
65 | print(i, htmlpages[i][0], htmlpages[i][1])
66 | self.save(i, htmlstart, htmlend, html[htmlpages[i][0]:htmlpages[i][1]])
67 | def main():
68 | cls = Main()
69 | cls.main()
70 | main()
71 |
--------------------------------------------------------------------------------
/litehtml-wx-print.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env vpython3
2 | import sys
3 | import wx
4 |
5 | import logme
6 | from litehtmlpy import litehtmlwx, litehtmlpy
7 |
8 | class document_container(litehtmlwx.document_container):
9 | def pt_to_px(self, pt):
10 | return pt
11 |
12 | class HTMLPrinterPrintout(wx.Printout):
13 | def __init__(self, htmlstart, htmlend, html, htmlpages):
14 | wx.Printout.__init__(self)
15 | self.npages = len(htmlpages)
16 | self.htmlstart = htmlstart
17 | self.htmlend = htmlend
18 | self.html = html
19 | self.htmlpages = htmlpages
20 | self.pageinfo = (1, self.npages, 1, self.npages)
21 |
22 | def OnBeginDocument(self, start, end):
23 | return self.GetDC().StartDoc('DEMO')
24 |
25 | def HasPage(self, page):
26 | return page <= self.npages
27 |
28 | def GetPageInfo(self):
29 | return self.pageinfo
30 |
31 | def OnPreparePrinting(self):
32 | pass
33 |
34 | def OnPrintPage(self, page):
35 | dc = self.GetDC()
36 | self.MakePage(dc, page)
37 | return True
38 |
39 | def MakePage(self, dc, page):
40 | page -= 1
41 | print('page:', page, self.htmlpages[page])
42 | dc.Clear()
43 | dc.SetMapMode(wx.MM_TEXT)
44 | dc.SetBackground(wx.Brush(wx.WHITE))
45 |
46 | (w, h) = dc.GetSize()
47 | print('DC:', 'w=', w, 'h=', h, 'ppi=', dc.GetPPI())
48 |
49 | html = self.html[self.htmlpages[page][0]:self.htmlpages[page][1]]
50 | html = self.htmlstart + html + self.htmlend
51 |
52 | cntr = document_container()
53 | cntr.SetDC(dc)
54 | doc = litehtmlpy.fromString(cntr, html, None, None)
55 | try:
56 | doc.render(cntr.size[0], litehtmlpy.render_all)
57 | cntr.size[1] = doc.height()
58 |
59 | if 1:
60 | print('DOC:', 'w=', doc.width(), 'h=', doc.height())
61 | maxX = doc.width() + (2*50) # marginesy 50 device units
62 | maxY = doc.height() + (2*50) # marginesy 50 device units
63 | (w, h) = dc.GetSize()
64 | scaleX = float(w) / maxX
65 | scaleY = float(h) / maxY
66 | actualScale = min(scaleX, scaleY)
67 | print('SCALE:', 'x=', scaleX, 'y=', scaleY, 'act=', actualScale)
68 | dc.SetUserScale(actualScale, actualScale)
69 | #dc.SetDeviceOrigin(int(posX), int(posY))
70 |
71 | #bmp = wx.Bitmap(cntr.size[0], cntr.size[1], 32)
72 | clip = litehtmlpy.position(0, 0, doc.width(), doc.height())
73 | doc.draw(0, 0, 0, clip)
74 | finally:
75 | cntr.SetDC(None)
76 | del doc
77 | del cntr
78 |
79 | class HTMLPrintDokument:
80 | def __init__(self, fname):
81 | self.fname = fname
82 |
83 | def Run(self):
84 | html = open(self.fname, 'rt').read()
85 | split=''
86 | start = html.find(split) + len(split)
87 | end = html.find('')
88 | htmlstart = html[0:start]
89 | htmlend = html[end:]
90 | html = html[start:end]
91 | htmlpages = []
92 | split = ''
93 | start = 0
94 | while True:
95 | stop = html.find(split, start)
96 | if stop == -1:
97 | htmlpages.append((start, len(html)))
98 | break
99 | htmlpages.append((start, stop))
100 | start = stop+len(split)
101 | npages = len(htmlpages)
102 |
103 | pdd = wx.PrintDialogData()
104 | pdd.SetMaxPage(npages)
105 | pdd.SetToPage(npages)
106 | printer = wx.Printer(pdd)
107 | printout = HTMLPrinterPrintout(htmlstart, htmlend, html, htmlpages)
108 | if not printer.Print(None, printout, 1):
109 | rc = wx.Printer_GetLastError()
110 | else:
111 | rc = wx.PRINTER_NO_ERROR
112 | printout.Destroy()
113 | print('rc=', rc)
114 |
115 | class Main:
116 | def demo(self):
117 | wxapp = wx.App(False)
118 |
119 | if len(sys.argv) > 1:
120 | fname = sys.argv[1]
121 | else:
122 | fname = 'demo.html'
123 | cls = HTMLPrintDokument(fname)
124 | cls.Run()
125 |
126 | def main():
127 | app = Main()
128 | app.demo()
129 |
130 | main()
131 |
--------------------------------------------------------------------------------
/litehtmld.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env vpython3
2 | import sys
3 | import gc
4 | gc.enable()
5 |
6 | import logme
7 |
8 | from litehtmlpy import litehtml, litehtmlpy
9 |
10 | class document_container(litehtml.document_container):
11 | pass
12 |
13 | def main():
14 | #litehtml.litehtml.liblitehtmlpy.debuglog(1)
15 | if len(sys.argv) > 1:
16 | fname = sys.argv[1]
17 | else:
18 | fname = 'demo.html'
19 | html = open(fname, 'rt').read()
20 | cntr = document_container()
21 | try:
22 | doc = litehtmlpy.fromString(cntr, html, None, None)
23 | try:
24 | doc.render(cntr.size[0], litehtmlpy.render_all)
25 | clip = litehtmlpy.position(0, 0, doc.width(), doc.height())
26 | doc.draw(0, 0, 0, clip)
27 | del clip
28 | print('done')
29 | finally:
30 | print('del doc')
31 | del doc
32 | finally:
33 | print('del cntr')
34 | del cntr
35 |
36 | showobjs = False
37 | print('gc.collect', gc.collect())
38 | print('gc.get_count=', gc.get_count())
39 | print('gc.get_stats=', gc.get_stats())
40 | if showobjs:
41 | print('gc.get_objects=', gc.get_objects())
42 | main()
43 | print('gc.collect', gc.collect())
44 | print('gc.get_count=', gc.get_count())
45 | print('gc.get_stats=', gc.get_stats())
46 | if showobjs:
47 | print('gc.get_objects=', gc.get_objects())
48 |
--------------------------------------------------------------------------------
/litehtmlt.html:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 |
16 |
17 | zażółcić gęślą jaźń
18 | web-platform-tests.org
19 | W3C tests
20 | W3C test flex-direction
21 |
22 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/litehtmlt.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env vpython3
2 | import logme
3 |
4 | from litehtmlpy import litehtml, litehtmlpy
5 |
6 | class Button(litehtml.litehtmlpy.html_tag):
7 | def __init__(self, attributes, doc):
8 | super().__init__(doc)
9 | print('i`m the button', attributes, 'tagName=', self.get_tagName())
10 | def draw(self, hdc, x, y, clip, ri):
11 | super().draw(hdc, x, y, clip, ri)
12 | print('Button.draw', hdc, x, y, clip, ri.pos())
13 |
14 | class document_container(litehtml.document_container):
15 | def __init__(self):
16 | super().__init__()
17 | self.handlers = []
18 |
19 | def import_css(self, text, url, base_url):
20 | url = urllib.parse.urljoin(base_url, url)
21 | if os.path.exists(url):
22 | with open(url, 'rt') as fp:
23 | data = fp.read()
24 | elif url.split(':')[0] in ('http', 'https'):
25 | r = requests.get(url)
26 | if r.headers['Content-Type'] == 'text/css':
27 | data = r.text
28 | if data is None:
29 | print('unknown import_css', url)
30 | return
31 | return data
32 |
33 | def create_element(self, tag_name, attributes=None, doc=None):
34 | if tag_name == 'button':
35 | if doc is not None:
36 | tagh = Button(attributes, doc)
37 | self.handlers.append(tagh)
38 | return tagh
39 | return True
40 |
41 | def main():
42 | #litehtml.litehtml.liblitehtmlpy.debuglog(1)
43 | html = open('litehtmlt.html', 'rt').read()
44 | cntr = document_container()
45 |
46 | doc = litehtmlpy.fromString(cntr, html, None, None)
47 | doc.render(cntr.size[0], litehtmlpy.render_all)
48 | clip = litehtmlpy.position(0, 0, doc.width(), doc.height())
49 | doc.draw(0, 0, 0, clip)
50 | del doc, cntr
51 |
52 | main()
53 |
--------------------------------------------------------------------------------
/logme.py:
--------------------------------------------------------------------------------
1 | import logging
2 | logger = logging.getLogger()
3 | handler = logging.StreamHandler()
4 | formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
5 | handler.setFormatter(formatter)
6 | logger.addHandler(handler)
7 | logger.setLevel(logging.DEBUG)
8 |
9 | if 1:
10 | import sys
11 | sys.path.insert(1, 'src')
12 |
13 | from litehtmlpy import litehtmlpy
14 | #litehtmlpy.debuglog(1)
15 |
--------------------------------------------------------------------------------
/mingw-cross.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # https://cmake.org/cmake/help/book/mastering-cmake/chapter/Cross%20Compiling%20With%20CMake.html
3 | #
4 | # the name of the target operating system
5 | set(CMAKE_SYSTEM_NAME Windows)
6 |
7 | # which compilers to use for C and C++
8 | set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
9 | set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
10 |
11 | # where is the target environment located
12 | set(CMAKE_FIND_ROOT_PATH /usr/bin
13 | /devel/bin/mingw-install)
14 |
15 | # adjust the default behavior of the FIND_XXX() commands:
16 | # search programs in the host environment
17 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
18 |
19 | # search headers and libraries in the target environment
20 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
21 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
22 |
23 | #set(CMAKE_BUILD_TYPE DEBUG)
24 | #set(CMAKE_C_FLAGS "-O0 -ggdb")
25 | #set(CMAKE_C_FLAGS_DEBUG "-O0 -ggdb")
26 | #set(CMAKE_C_FLAGS_RELEASE "-O0 -ggdb")
27 | #set(CMAKE_CXX_FLAGS "-O0 -ggdb")
28 | #set(CMAKE_CXX_FLAGS_DEBUG "-O0 -ggdb")
29 | #set(CMAKE_CXX_FLAGS_RELEASE "-O0 -ggdb")
30 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static -static-libgcc -march=x86-64 -mtune=generic")
31 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static -static-libgcc -static-libstdc++ -march=x86-64 -mtune=generic")
32 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib -static -static-libgcc -static-libstdc++ -march=x86-64 -mtune=generic")
33 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = [
3 | "setuptools>=42",
4 | "wheel",
5 | "ninja",
6 | "cmake>=3.12",
7 | "pybind11",
8 | ]
9 | build-backend = "setuptools.build_meta"
10 |
11 | [project]
12 | name = "litehtmlpy"
13 | version = "0.1.0"
14 | authors = [
15 | { name="Grzegorz Makarewicz", email="mak@trisoft.com.pl" },
16 | ]
17 | description = "LiteHtmlPy is a solution that helps python developers to create printouts and previews of html5/css3 pages without using a web browser."
18 | readme = "README.md"
19 | requires-python = ">=3.7"
20 | classifiers=[
21 | 'Development Status :: 4 - Beta',
22 | 'Development Status :: 5 - Production/Stable',
23 | 'Operating System :: OS Independent',
24 | 'Intended Audience :: Developers',
25 | 'License :: OSI Approved :: MIT License',
26 | 'Programming Language :: Python :: 3',
27 | 'Topic :: Software Development :: Libraries :: Python Modules',
28 | 'Topic :: Office/Business',
29 | 'Topic :: Text Processing',
30 | 'Topic :: Multimedia :: Graphics',
31 | ]
32 |
33 | [project.urls]
34 | "Homepage" = "https://github.com/m32/litehtmlpy"
35 | "Bug Tracker" = "https://github.com/m32/litehtmlpy/issues"
36 |
--------------------------------------------------------------------------------
/set_inner_html.cpp:
--------------------------------------------------------------------------------
1 | void litehtml::element::set_inner_html(const litehtml::tchar_t* str, litehtml::css* user_styles)
2 | {
3 | // parse document into GumboOutput
4 | GumboOptions options = kGumboDefaultOptions;
5 |
6 | options.fragment_context = gumbo_tag_enum(get_tagName());
7 |
8 | GumboOutput* output = gumbo_parse_with_options(&options, str, strlen(str));
9 | document::ptr doc = m_doc.lock();
10 |
11 | m_children.clear();
12 |
13 | document::create_node(output->root->v.element.children.data[0], m_children, true, doc);
14 | gumbo_destroy_output(&kGumboDefaultOptions, output);
15 |
16 | for (auto& child : m_children)
17 | {
18 | child->parent(shared_from_this());
19 | }
20 |
21 | if( doc->is_initialised() )
22 | {
23 | for (auto& child : m_children)
24 | {
25 | // Let's process created elements tree
26 | // apply master CSS
27 | child->apply_stylesheet(doc->get_context()->master_css());
28 |
29 | // parse elements attributes
30 | child->parse_attributes();
31 |
32 | // Apply parsed styles.
33 | child->apply_stylesheet(doc->get_styles());
34 |
35 | // Apply user styles if any
36 | if (user_styles)
37 | {
38 | child->apply_stylesheet(*user_styles);
39 | }
40 |
41 | // Parse applied styles in the elements
42 | child->parse_styles();
43 |
44 | // Finally initialize elements
45 | child->init();
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import subprocess
4 | import sys
5 | from pathlib import Path
6 |
7 | from setuptools import Extension, setup, find_packages
8 | from setuptools.command.build_ext import build_ext
9 |
10 | from pybind11 import get_include
11 |
12 | # Convert distutils Windows platform specifiers to CMake -A arguments
13 | PLAT_TO_CMAKE = {
14 | "win32": "Win32",
15 | "win-amd64": "x64",
16 | "win-arm32": "ARM",
17 | "win-arm64": "ARM64",
18 | }
19 |
20 |
21 | # A CMakeExtension needs a sourcedir instead of a file list.
22 | # The name must be the _single_ output extension from the CMake build.
23 | # If you need multiple extensions, see scikit-build.
24 | class CMakeExtension(Extension):
25 | def __init__(self, name: str, sourcedir: str = "") -> None:
26 | super().__init__(name, sources=[])
27 | self.sourcedir = os.fspath(Path(sourcedir).resolve())
28 |
29 |
30 | class CMakeBuild(build_ext):
31 | def build_extension(self, ext: CMakeExtension) -> None:
32 | # Must be in this form due to bug in .resolve() only fixed in Python 3.10+
33 | ext_fullpath = Path.cwd() / self.get_ext_fullpath(ext.name) # type: ignore[no-untyped-call]
34 | extdir = ext_fullpath.parent.resolve()
35 |
36 | # Using this requires trailing slash for auto-detection & inclusion of
37 | # auxiliary "native" libs
38 |
39 | debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug
40 | cfg = "Debug" if debug else "Release"
41 |
42 | # CMake lets you override the generator - we need to check this.
43 | # Can be set with Conda-Build, for example.
44 | cmake_generator = os.environ.get("CMAKE_GENERATOR", "")
45 |
46 | # Set Python_EXECUTABLE instead if you use PYBIND11_FINDPYTHON
47 | # EXAMPLE_VERSION_INFO shows you how to pass a value into the C++ code
48 | # from Python.
49 | cname = ext.name
50 | cmake_args = [
51 | f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}{os.sep}{cname}{os.sep}",
52 | f"-DPYTHON_EXECUTABLE={sys.executable}",
53 | f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm
54 | "-DPYBIND11_INCLUDE_DIR={}".format(get_include()),
55 | "-DLITEHTML_BUILD_TESTING=OFF",
56 | "-DLITEHTMLPY_BUILD_TESTING=OFF",
57 | "-Wno-dev",
58 | ]
59 | build_args = []
60 | # Adding CMake arguments set as environment variable
61 | # (needed e.g. to build for ARM OSx on conda-forge)
62 | if "CMAKE_ARGS" in os.environ:
63 | cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") if item]
64 |
65 | if self.compiler.compiler_type != "msvc":
66 | # Using Ninja-build since it a) is available as a wheel and b)
67 | # multithreads automatically. MSVC would require all variables be
68 | # exported for Ninja to pick it up, which is a little tricky to do.
69 | # Users can override the generator with CMAKE_GENERATOR in CMake
70 | # 3.15+.
71 | if not cmake_generator or cmake_generator == "Ninja":
72 | try:
73 | import ninja # noqa: F401
74 |
75 | ninja_executable_path = Path(ninja.BIN_DIR) / "ninja"
76 | cmake_args += [
77 | "-GNinja",
78 | f"-DCMAKE_MAKE_PROGRAM:FILEPATH={ninja_executable_path}",
79 | ]
80 | except ImportError:
81 | pass
82 |
83 | else:
84 |
85 | # Single config generators are handled "normally"
86 | single_config = any(x in cmake_generator for x in {"NMake", "Ninja"})
87 |
88 | # CMake allows an arch-in-generator style for backward compatibility
89 | contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"})
90 |
91 | # Specify the arch if using MSVC generator, but only if it doesn't
92 | # contain a backward-compatibility arch spec already in the
93 | # generator name.
94 | if not single_config and not contains_arch:
95 | cmake_args += ["-A", PLAT_TO_CMAKE[self.plat_name]]
96 |
97 | # Multi-config generators have a different way to specify configs
98 | if not single_config:
99 | cmake_args += [
100 | f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{cfg.upper()}={extdir}"
101 | ]
102 | build_args += ["--config", cfg]
103 |
104 | if sys.platform.startswith("darwin"):
105 | # Cross-compile support for macOS - respect ARCHFLAGS if set
106 | archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", ""))
107 | if archs:
108 | cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))]
109 |
110 | # Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level
111 | # across all generators.
112 | if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ:
113 | # self.parallel is a Python 3 only way to set parallel jobs by hand
114 | # using -j in the build_ext call, not supported by pip or PyPA-build.
115 | if hasattr(self, "parallel") and self.parallel:
116 | # CMake 3.12+ only.
117 | build_args += [f"-j{self.parallel}"]
118 |
119 | subprocess.run(
120 | ["cmake", ext.sourcedir, *cmake_args], check=True
121 | )
122 | subprocess.run(
123 | ["cmake", "--build", ".", *build_args], check=True
124 | )
125 |
126 |
127 | # The information here can also be placed in setup.cfg - better separation of
128 | # logic and declaration, and simpler if you include description/version in a file.
129 | setup(
130 | name="litehtmlpy",
131 | version="0.1.0",
132 | author="Grzegorz Makarewicz",
133 | author_email="mak@trisoft.com.pl",
134 | description="LiteHtmlPy is a solution that helps python developers to create printouts and previews of html5/css3 pages without using a web browser.",
135 | long_description="",
136 | ext_modules=[
137 | CMakeExtension("litehtmlpy.litehtmlpy")
138 | ],
139 | cmdclass={"build_ext": CMakeBuild},
140 | zip_safe=False,
141 | # extras_require={"test": ["pytest>=6.0"]},
142 | python_requires=">=3.7",
143 | packages=find_packages(where="src"),
144 | package_dir={"": "src"},
145 | )
146 |
--------------------------------------------------------------------------------
/src/background.h:
--------------------------------------------------------------------------------
1 | py::class_(m, "background_layer")
2 | .def_readwrite("border_box", &lh::background_layer::border_box)
3 | .def_readwrite("border_radius", &lh::background_layer::border_radius)
4 | .def_readwrite("clip_box", &lh::background_layer::clip_box)
5 | .def_readwrite("origin_box", &lh::background_layer::origin_box)
6 | .def_readwrite("attachment", &lh::background_layer::attachment)
7 | .def_readwrite("repeat", &lh::background_layer::repeat)
8 | .def_readwrite("is_root", &lh::background_layer::is_root)
9 | //
10 | .def(py::init<>())
11 | ;
12 | py::class_(m, "background_layer_image")
13 | .def_readwrite("url", &lh::background_layer::image::url)
14 | .def_readwrite("base_url", &lh::background_layer::image::base_url)
15 | ;
16 | py::class_(m, "background_layer_color_point")
17 | .def_readwrite("offset", &lh::background_layer::color_point::offset)
18 | .def_readwrite("color", &lh::background_layer::color_point::color)
19 | .def_readwrite("hint", &lh::background_layer::color_point::hint)
20 | //
21 | .def(py::init<>())
22 | .def(py::init(), "offset"_a, "color"_a)
23 | ;
24 | py::class_(m, "background_layer_color")
25 | .def_readwrite("color", &lh::background_layer::color::color)
26 | ;
27 | /*
28 | class gradient_base
29 | {
30 | public:
31 | vector color_points;
32 | color_space_t color_space = color_space_none;
33 | hue_interpolation_t hue_interpolation = hue_interpolation_none;
34 |
35 | void color_points_transparent_fix();
36 | bool prepare_color_points(float len, string_id grad_type, const vector& colors);
37 | };
38 |
39 | class linear_gradient : public gradient_base
40 | {
41 | public:
42 | pointF start;
43 | pointF end;
44 | };
45 |
46 | class radial_gradient : public gradient_base
47 | {
48 | public:
49 | pointF position;
50 | pointF radius;
51 | };
52 |
53 | class conic_gradient : public gradient_base
54 | {
55 | public:
56 | pointF position;
57 | float angle = 0;
58 | };
59 | */
60 | py::enum_(m, "layer_type")
61 | .value("type_none", lh::background::layer_type::type_none)
62 | .value("type_color", lh::background::layer_type::type_color)
63 | .value("type_image", lh::background::layer_type::type_image)
64 | .value("type_linear_gradient", lh::background::layer_type::type_linear_gradient)
65 | .value("type_radial_gradient", lh::background::layer_type::type_radial_gradient)
66 | .value("type_conic_gradient", lh::background::layer_type::type_conic_gradient)
67 | ;
68 |
69 | py::class_(m, "background")
70 | .def_readwrite("m_image", &lh::background::m_image)
71 | .def_readwrite("m_baseurl", &lh::background::m_baseurl)
72 | .def_readwrite("m_color", &lh::background::m_color)
73 | .def_readwrite("m_attachment", &lh::background::m_attachment)
74 | .def_readwrite("m_position_x", &lh::background::m_position_x)
75 | .def_readwrite("m_position_y", &lh::background::m_position_y)
76 | .def_readwrite("m_size", &lh::background::m_size)
77 | .def_readwrite("m_repeat", &lh::background::m_repeat)
78 | .def_readwrite("m_clip", &lh::background::m_clip)
79 | .def_readwrite("m_origin", &lh::background::m_origin)
80 | //
81 | .def("is_empty", &lh::background::is_empty)
82 | .def("get_layers_number", &lh::background::get_layers_number)
83 | .def("get_layer", &lh::background::get_layer, "idx"_a, "pos"_a, "el"_a, "ri"_a, "layer"_a)
84 | .def("get_layer_type", &lh::background::get_layer_type, "idx"_a)
85 | .def("get_image_layer", &lh::background::get_image_layer, "idx"_a)
86 | .def("get_color_layer", &lh::background::get_color_layer, "idx"_a)
87 | .def("get_linear_gradient_layer", &lh::background::get_linear_gradient_layer, "idx"_a, "layer"_a)
88 | .def("get_radial_gradient_layer", &lh::background::get_radial_gradient_layer, "idx"_a, "layer"_a)
89 | .def("get_conic_gradient_layer", &lh::background::get_conic_gradient_layer, "idx"_a, "layer"_a)
90 | .def("draw_layer", &lh::background::draw_layer, "hdc"_a, "idx"_a, "layer"_a, "container"_a)
91 | ;
92 |
--------------------------------------------------------------------------------
/src/borders.h:
--------------------------------------------------------------------------------
1 | py::class_(m, "css_border")
2 | .def_readwrite("width", &lh::css_border::width)
3 | .def_readwrite("style", &lh::css_border::style)
4 | .def_readwrite("style", &lh::css_border::color)
5 | //
6 | .def(py::init<>())
7 | .def(py::init())
8 | //css_border& operator=(const css_border& val)
9 | .def("to_string", &lh::css_border::to_string)
10 | ;
11 |
12 | py::class_(m, "border")
13 | .def_readwrite("width", &lh::border::width)
14 | .def_readwrite("style", &lh::border::style)
15 | .def_readwrite("color", &lh::border::color)
16 | //
17 | .def(py::init<>())
18 | .def(py::init())
19 | .def(py::init())
20 | //border& operator=(const border& val)
21 | //border& operator=(const css_border& val)
22 | ;
23 |
24 | py::class_(m, "border_radiuses")
25 | .def_readwrite("top_left_x", &lh::border_radiuses::top_left_x)
26 | .def_readwrite("top_left_y", &lh::border_radiuses::top_left_y)
27 | .def_readwrite("top_right_x", &lh::border_radiuses::top_right_x)
28 | .def_readwrite("top_right_y", &lh::border_radiuses::top_right_y)
29 | .def_readwrite("bottom_right_x", &lh::border_radiuses::bottom_right_x)
30 | .def_readwrite("bottom_right_y", &lh::border_radiuses::bottom_right_y)
31 | .def_readwrite("bottom_left_x", &lh::border_radiuses::bottom_left_x)
32 | .def_readwrite("bottom_left_y", &lh::border_radiuses::bottom_left_y)
33 | //
34 | .def(py::init<>())
35 | .def(py::init())
36 | //border_radiuses& operator = (const border_radiuses& val)
37 | //void operator += (const margins& mg)
38 | //void operator -= (const margins& mg)
39 | //.def("fix_values", &lh::border_radiuses::fix_values)
40 | //void fix_values(int width, int height)
41 | ;
42 |
43 | py::class_(m, "css_border_radius")
44 | .def_readwrite("top_left_x", &lh::css_border_radius::top_left_x)
45 | .def_readwrite("top_left_y", &lh::css_border_radius::top_left_y)
46 | .def_readwrite("top_right_x", &lh::css_border_radius::top_right_x)
47 | .def_readwrite("top_right_y", &lh::css_border_radius::top_right_y)
48 | .def_readwrite("bottom_right_x", &lh::css_border_radius::bottom_right_x)
49 | .def_readwrite("bottom_right_y", &lh::css_border_radius::bottom_right_y)
50 | .def_readwrite("bottom_left_x", &lh::css_border_radius::bottom_left_x)
51 | .def_readwrite("bottom_left_y", &lh::css_border_radius::bottom_left_y)
52 | //
53 | .def(py::init<>())
54 | .def(py::init())
55 | //css_border_radius& operator=(const css_border_radius& val)
56 | //border_radiuses calc_percents(int width, int height) const
57 | ;
58 |
59 | py::class_(m, "css_borders")
60 | .def_readwrite("left", &lh::css_borders::left)
61 | .def_readwrite("top", &lh::css_borders::top)
62 | .def_readwrite("right", &lh::css_borders::right)
63 | .def_readwrite("bottom", &lh::css_borders::bottom)
64 | .def_readwrite("radius", &lh::css_borders::radius)
65 | //
66 | //?css_borders() = default;
67 | .def(py::init<>())
68 | //.def(py::init())
69 | .def("is_visible", &lh::css_borders::is_visible)
70 | //css_borders& operator=(const css_borders& val)
71 | .def("to_string", &lh::css_borders::to_string)
72 | ;
73 |
74 | py::class_(m, "borders")
75 | .def_readwrite("left", &lh::borders::left)
76 | .def_readwrite("top", &lh::borders::top)
77 | .def_readwrite("right", &lh::borders::right)
78 | .def_readwrite("bottom", &lh::borders::bottom)
79 | .def_readwrite("radius", &lh::borders::radius)
80 |
81 | //?borders() = default;
82 | .def(py::init<>())
83 | //.def(py::init())
84 | //.def(py::init())
85 | .def("is_visible", &lh::borders::is_visible)
86 | //borders& operator=(const borders& val)
87 | //borders& operator=(const css_borders& val)
88 | ;
89 |
--------------------------------------------------------------------------------
/src/cairo/cairo_borders.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "cairo_borders.h"
3 | #include
4 |
5 | #ifndef M_PI
6 | # define M_PI 3.14159265358979323846
7 | #endif
8 |
9 | void cairo::add_path_arc(cairo_t* cr, double x, double y, double rx, double ry, double a1, double a2, bool neg)
10 | {
11 | if(rx > 0 && ry > 0)
12 | {
13 | cairo_save(cr);
14 |
15 | cairo_translate(cr, x, y);
16 | cairo_scale(cr, 1, ry / rx);
17 | cairo_translate(cr, -x, -y);
18 |
19 | if(neg)
20 | {
21 | cairo_arc_negative(cr, x, y, rx, a1, a2);
22 | } else
23 | {
24 | cairo_arc(cr, x, y, rx, a1, a2);
25 | }
26 |
27 | cairo_restore(cr);
28 | } else
29 | {
30 | cairo_move_to(cr, x, y);
31 | }
32 | }
33 |
34 | /**
35 | * Draw border at the left side. Use the only function to draw all border by using rotation transfer
36 | * @param cr cairo context
37 | * @param left left position of the border
38 | * @param top top position of the border
39 | * @param bottom bottom position of the border
40 | * @param data border data
41 | */
42 | void cairo::border::draw_border()
43 | {
44 | cairo_save(cr);
45 |
46 | if(radius_top_x && radius_top_y)
47 | {
48 | double start_angle = M_PI;
49 | double end_angle = start_angle + M_PI / 2.0 / ((double) top_border_width / (double) border_width + 1);
50 |
51 | add_path_arc(cr,
52 | left + radius_top_x,
53 | top + radius_top_y,
54 | radius_top_x - border_width,
55 | radius_top_y - border_width + (border_width - top_border_width),
56 | start_angle,
57 | end_angle, false);
58 |
59 | add_path_arc(cr,
60 | left + radius_top_x,
61 | top + radius_top_y,
62 | radius_top_x,
63 | radius_top_y,
64 | end_angle,
65 | start_angle, true);
66 | } else
67 | {
68 | cairo_move_to(cr, left + border_width, top + top_border_width);
69 | cairo_line_to(cr, left, top);
70 | }
71 |
72 | if(radius_bottom_x && radius_bottom_y)
73 | {
74 | cairo_line_to(cr, left, bottom - radius_bottom_y);
75 |
76 | double end_angle = M_PI;
77 | double start_angle = end_angle - M_PI / 2.0 / ((double) bottom_border_width / (double) border_width + 1);
78 |
79 | add_path_arc(cr,
80 | left + radius_bottom_x,
81 | bottom - radius_bottom_y,
82 | radius_bottom_x,
83 | radius_bottom_y,
84 | end_angle,
85 | start_angle, true);
86 |
87 | add_path_arc(cr,
88 | left + radius_bottom_x,
89 | bottom - radius_bottom_y,
90 | radius_bottom_x - border_width,
91 | radius_bottom_y - border_width + (border_width - bottom_border_width),
92 | start_angle,
93 | end_angle, false);
94 | } else
95 | {
96 | cairo_line_to(cr, left, bottom);
97 | cairo_line_to(cr, left + border_width, bottom - bottom_border_width);
98 | }
99 | cairo_clip(cr);
100 |
101 | switch (style)
102 | {
103 | case litehtml::border_style_dotted:
104 | draw_dotted();
105 | break;
106 | case litehtml::border_style_dashed:
107 | draw_dashed();
108 | break;
109 | case litehtml::border_style_double:
110 | draw_double();
111 | break;
112 | case litehtml::border_style_inset:
113 | draw_inset_outset(true);
114 | break;
115 | case litehtml::border_style_outset:
116 | draw_inset_outset(false);
117 | break;
118 | case litehtml::border_style_groove:
119 | draw_groove_ridge(true);
120 | break;
121 | case litehtml::border_style_ridge:
122 | draw_groove_ridge(false);
123 | break;
124 | default:
125 | draw_solid();
126 | break;
127 | }
128 |
129 | cairo_restore(cr);
130 | }
131 |
132 | void cairo::border::draw_line(double line_offset, double top_line_offset, double bottom_line_offset)
133 | {
134 | if(radius_top_x && radius_top_y)
135 | {
136 | double end_angle = M_PI;
137 | double start_angle = end_angle + M_PI / 2.0 / ((double) top_border_width / (double) border_width + 1);
138 |
139 | add_path_arc(cr,
140 | left + radius_top_x,
141 | top + radius_top_y,
142 | radius_top_x - line_offset,
143 | radius_top_y - line_offset + (line_offset - top_line_offset),
144 | start_angle,
145 | end_angle, true);
146 | } else
147 | {
148 | cairo_move_to(cr, left + line_offset, top);
149 | }
150 |
151 | if(radius_bottom_x && radius_bottom_y)
152 | {
153 | cairo_line_to(cr, left + line_offset, bottom - radius_bottom_y);
154 |
155 | double start_angle = M_PI;
156 | double end_angle = start_angle - M_PI / 2.0 / ((double) bottom_border_width / (double) border_width + 1);
157 |
158 | add_path_arc(cr,
159 | left + radius_bottom_x,
160 | bottom - radius_bottom_y,
161 | radius_bottom_x - line_offset,
162 | radius_bottom_y - line_offset + (line_offset - bottom_line_offset),
163 | start_angle,
164 | end_angle, true);
165 | } else
166 | {
167 | cairo_line_to(cr, left + line_offset, bottom);
168 | }
169 | }
170 |
171 | void cairo::border::draw_inset_outset(bool is_inset)
172 | {
173 | litehtml::web_color line_color;
174 | litehtml::web_color light_color = color;
175 | litehtml::web_color dark_color = color.darken(0.33);
176 | if(color.red == 0 && color.green == 0 && color.blue == 0)
177 | {
178 | dark_color.red = dark_color.green = dark_color.blue = 0x4C;
179 | light_color.red = light_color.green = light_color.blue = 0xB2;
180 | }
181 |
182 | if (real_side == left_side || real_side == top_side)
183 | {
184 | line_color = is_inset ? dark_color : light_color;
185 | } else
186 | {
187 | line_color = is_inset ? light_color : dark_color;
188 | }
189 | draw_line(border_width / 2.0,
190 | top_border_width / 2.0,
191 | bottom_border_width / 2.0);
192 | cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
193 | cairo_set_dash(cr, nullptr, 0, 0);
194 | set_color(cr, line_color);
195 | cairo_set_line_width(cr, border_width);
196 | cairo_stroke(cr);
197 | }
198 |
199 | void cairo::border::draw_double()
200 | {
201 | if (border_width < 3)
202 | {
203 | draw_solid();
204 | } else
205 | {
206 | cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
207 | cairo_set_dash(cr, nullptr, 0, 0);
208 | set_color(cr, color);
209 |
210 | double line_width = border_width / 3.0;
211 | cairo_set_line_width(cr, line_width);
212 | // draw external line
213 | draw_line(line_width / 2.0,
214 | top_border_width / 6.0,
215 | bottom_border_width / 6.0);
216 | cairo_stroke(cr);
217 | // draw internal line
218 | draw_line(border_width - line_width / 2.0,
219 | top_border_width - top_border_width / 6.0,
220 | bottom_border_width - bottom_border_width / 6.0);
221 | cairo_stroke(cr);
222 | }
223 | }
224 |
225 | void cairo::border::draw_dashed()
226 | {
227 | int line_length = std::abs(bottom - top);
228 | if(!line_length) return;
229 |
230 | draw_line(border_width / 2.0,
231 | top_border_width / 2.0,
232 | bottom_border_width / 2.0);
233 |
234 | int segment_length = border_width * 3;
235 | int seg_nums = line_length / segment_length;
236 | if(seg_nums < 2)
237 | {
238 | seg_nums = 2;
239 | } if(seg_nums % 2 != 0)
240 | {
241 | seg_nums = seg_nums + 1;
242 | }
243 | seg_nums++;
244 | double seg_len = (double) line_length / (double) seg_nums;
245 |
246 | double dashes[2];
247 | dashes[0] = seg_len;
248 | dashes[1] = seg_len;
249 | cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
250 | cairo_set_dash(cr, dashes, 2, 0);
251 | set_color(cr, color);
252 | cairo_set_line_width(cr, border_width);
253 | cairo_stroke(cr);
254 | }
255 |
256 | void cairo::border::draw_solid()
257 | {
258 | draw_line(border_width / 2.0,
259 | top_border_width / 2.0,
260 | bottom_border_width / 2.0);
261 | cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
262 | cairo_set_dash(cr, nullptr, 0, 0);
263 | set_color(cr, color);
264 | cairo_set_line_width(cr, border_width);
265 | cairo_stroke(cr);
266 | }
267 |
268 | void cairo::border::draw_dotted()
269 | {
270 | // Zero length line
271 | if(bottom == top) return;
272 |
273 | draw_line(border_width / 2.0,
274 | top_border_width / 2.0,
275 | bottom_border_width / 2.0);
276 |
277 | double line_length = std::abs(bottom - top);
278 |
279 | double dot_size = border_width;
280 | int num_dots = (int) std::nearbyint(line_length / (dot_size * 2.0));
281 | if(num_dots < 2)
282 | {
283 | num_dots = 2;
284 | } if(num_dots % 2 != 0)
285 | {
286 | num_dots = num_dots + 1;
287 | }
288 | num_dots++;
289 | double space_len = ((double) line_length - (double) border_width) / (num_dots - 1.0);
290 |
291 | double dashes[2];
292 | dashes[0] = 0;
293 | dashes[1] = space_len;
294 | cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
295 | cairo_set_dash(cr, dashes, 2, -dot_size / 2.0);
296 |
297 | set_color(cr, color);
298 | cairo_set_line_width(cr, border_width);
299 | cairo_stroke(cr);
300 | }
301 |
302 | void cairo::border::draw_groove_ridge(bool is_groove)
303 | {
304 | if(border_width == 1)
305 | {
306 | draw_solid();
307 | } else
308 | {
309 | litehtml::web_color inner_line_color;
310 | litehtml::web_color outer_line_color;
311 | litehtml::web_color light_color = color;
312 | litehtml::web_color dark_color = color.darken(0.33);
313 | if (color.red == 0 && color.green == 0 && color.blue == 0)
314 | {
315 | dark_color.red = dark_color.green = dark_color.blue = 0x4C;
316 | light_color.red = light_color.green = light_color.blue = 0xB2;
317 | }
318 |
319 | if (real_side == left_side || real_side == top_side)
320 | {
321 | outer_line_color = is_groove ? dark_color : light_color;
322 | inner_line_color = is_groove ? light_color : dark_color;
323 | } else
324 | {
325 | outer_line_color = is_groove ? light_color : dark_color;
326 | inner_line_color = is_groove ? dark_color : light_color;
327 | }
328 |
329 | cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
330 | cairo_set_dash(cr, nullptr, 0, 0);
331 |
332 | double line_width = border_width / 2.0;
333 | cairo_set_line_width(cr, line_width);
334 | // draw external line
335 | draw_line(line_width / 2.0,
336 | top_border_width / 4.0,
337 | bottom_border_width / 4.0);
338 | set_color(cr, outer_line_color);
339 | cairo_stroke(cr);
340 | // draw internal line
341 | set_color(cr, inner_line_color);
342 | draw_line(border_width - line_width / 2.0,
343 | top_border_width - top_border_width / 4.0,
344 | bottom_border_width - bottom_border_width / 4.0);
345 | cairo_stroke(cr);
346 | }
347 | }
348 |
--------------------------------------------------------------------------------
/src/cairo/cairo_borders.h:
--------------------------------------------------------------------------------
1 | #ifndef LITEHTML_CAIRO_BORDERS_H
2 | #define LITEHTML_CAIRO_BORDERS_H
3 |
4 | #include
5 |
6 | namespace cairo
7 | {
8 | extern void add_path_arc(cairo_t* cr, double x, double y, double rx, double ry, double a1, double a2, bool neg);
9 | inline void set_color(cairo_t* cr, const litehtml::web_color& color)
10 | {
11 | cairo_set_source_rgba(cr, color.red / 255.0, color.green / 255.0, color.blue / 255.0, color.alpha / 255.0);
12 | }
13 |
14 | class border
15 | {
16 | public:
17 | enum real_side_t
18 | {
19 | left_side,
20 | top_side,
21 | right_side,
22 | bottom_side
23 | };
24 |
25 | real_side_t real_side; /// real side of the border
26 | litehtml::web_color color;
27 | litehtml::border_style style;
28 |
29 | int border_width;
30 | int top_border_width;
31 | int bottom_border_width;
32 |
33 | int radius_top_x;
34 | int radius_top_y;
35 | int radius_bottom_x;
36 | int radius_bottom_y;
37 |
38 | border(cairo_t* _cr, int _left, int _top, int _bottom) :
39 | real_side(left_side),
40 | color(),
41 | style(litehtml::border_style_none),
42 | border_width(0),
43 | top_border_width(0),
44 | bottom_border_width(0),
45 | radius_top_x(0),
46 | radius_top_y(0),
47 | radius_bottom_x(0),
48 | radius_bottom_y(0),
49 | cr(_cr), left(_left), top(_top), bottom(_bottom)
50 | {}
51 |
52 | void draw_border();
53 |
54 | private:
55 | cairo_t* cr;
56 | int left;
57 | int top;
58 | int bottom;
59 | void draw_line(double line_offset, double top_line_offset, double bottom_line_offset);
60 | void draw_solid();
61 | void draw_dotted();
62 | void draw_dashed();
63 | void draw_double();
64 | void draw_inset_outset(bool is_inset);
65 | void draw_groove_ridge(bool is_groove);
66 | };
67 | }
68 |
69 | #endif //LITEHTML_CAIRO_BORDERS_H
70 |
--------------------------------------------------------------------------------
/src/cairo/cairo_images_cache.h:
--------------------------------------------------------------------------------
1 | #ifndef LITEHTML_CAIRO_IMAGES_CACHE_H
2 | #define LITEHTML_CAIRO_IMAGES_CACHE_H
3 |
4 | #include
5 | #include