Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • PantyNabber/fc-pregmod
  • pregmodfan/fc-pregmod
  • Alice.Grant/fc-pregmod
  • powerfful/fc-pregmod
  • elstumpo/fc-pregmod
  • Y/fc-pregmod
  • floer/fc-pregmod
  • oidocrop/fc-pregmod
  • hawk5005/fc-pregmod
  • nn/fc-pregmod
  • latios298/fc-pregmod
  • brpregmodfan/fc-pregmod
  • SomeoneTookMyUserName/fc-pregmod
  • 000-250-006/fc-pregmod
  • rewrica/fc-pregmod
  • Stuffedgame/fc-pregmod
  • wisepants314/fc-pregmod
  • fcanon/fc-pregmod
  • randomanon1/pregmod-mod-cyberfurry
  • teddy.buckland/fc-pregmod
  • farsinian_591b7a2d8b49d/fc-pregmod
  • FCShotadev/fc-pregmod
  • uselessartifact/fc-pregmod
  • irina_90/fc-pregmod
  • thaumx/fc-pregmod
  • MouseOfLight/fc-pregmod
  • empresssela/fc-pregmod
  • MasterAaran/fc-pregmod
  • ishy/fc-pregmod
  • psychofox/fc-pregmod
  • shadomancer/fc-pregmod
  • tycrakgg/fc-pregmod
  • azktaawc/fc-pregmod
  • andonno07/fc-pregmod
  • Onithyr/fc-pregmod
  • redneck987.jl/fc-pregmod
  • Farseeker/fc-pregmod
  • milliarc/fc-pregmod
  • BeefimusMaximus/fc-pregmod
  • magicknight79/fc-pregmod
  • hexall90/fc-pregmod
  • cantworkforever/fc-pregmod
  • jc052005/fc-pregmod
  • klorpa/fc-pregmod
  • doku/fc-pregmod
  • samhradh/fc-pregmod
  • scientist/fc-pregmod
  • albania420/fc-pregmod
  • Editoranon/fc-pregmod
  • Anony/fc-pregmod
  • deimios666/fc-pregmod
  • IvoHoe/fc-pregmod
  • bitty/fc-pregmod
  • RealAnon1800/fc-pregmod
  • brankirk/fc-pregmod
  • Amygdalan/fc-pregmod
  • DankWolf/fc-pregmod
  • Supot1951/fc-pregmod
  • bcy603/fc-pregmod
  • pwfxjpuv/fc-pregmod
  • ThreadAnon/fc-pregmod
  • Anon1800/fc-pregmod
  • Echoart/fc-pregmod
  • Dingotush/fc-pregmod
  • anonman/fc-pregmod
  • Arkerthan/fc-pregmod
  • svornost/fc-pregmod
  • wierdwierdos/fc-pregmod
  • wetwareAnon/fc-pregmod
  • QuartzHat/fc-pregmod
  • IchthysdeKilt/fc-pregmod
  • AnonAnonFC/fc-pregmod
  • Alexsis/fc-pregmod
  • LoyalTreeWP/fc-pregmod
  • aerialace/fc-pregmod
  • NurseryAnon/fc-pregmod
  • drakeashordcataclysm/fc-pregmod
  • AshVaris/fc-pregmod
  • purely0nothing/fc-pregmod
  • alex2011/fc-pregmod
  • Lindontree/fc-pregmod
  • FCaa/fc-pregmod
  • TR-8R/fc-pregmod
  • Jones/fc-pregmod
  • brr99/fc-pregmod
  • WriteAnon101/fc-pregmod
  • Drosil/fc-pregmod
  • Bob1221/fc-pregmod
  • vas/fc-pregmod
  • gitgud.user.937/fc-pregmod
  • D-K/fc-pregmod
  • AnonDev/fc-pregmod
  • madman23456/fc-pregmod
  • InarusLynx/fc-pregmod
  • Sonofrevvan/fc-pregmod
  • Randoisrando/fc-pregmod
  • cheez94/fc-pregmod
  • dldldl/fc-pregmod
  • alice321/fc-pregmod
  • Alexei91/fc-pregmod
  • darkcy/fc-pregmod
  • MapleMAD/fc-pregmod
  • pillarofsalt/fc-pregmod
  • vultureangels/fc-pregmod
  • kernel/fc-pregmod
  • nooneman/fc-pregmod
  • deepmurk/fc-pregmod
  • uglybead/fc-pregmod
  • lemongrab/fc-pregmod
  • temperence-chan/fc-pregmod
  • hcommenter/fc-pregmod
  • SpedeMemerson/fc-pregmod
  • qwijqwsf/fc-pregmod
  • BuDClow/fc-pregmod
  • HiveBro/fc-pregmod
  • shoku/fc-pregmod
  • ezsh/fc-pregmod
  • Blank/fc-pregmod
  • randoralcissian/fc-pregmod
  • benito92/fc-pregmod
  • balakart/fc-pregmod
  • wedonotsaw/fc-pregmod
  • Cayleth/fc-pregmod
  • Khip/fc-pregmod
  • Zfair/fc-pregmod
  • promethium/fc-pregmod
  • scyne/fc-pregmod
  • ZZC/fc-pregmod
  • SilverJanine/fc-pregmod
  • joxosix654email-9.co/fc-pregmod
  • Littlefootlittleguy/fc-pregmod
  • FelipeBA/fc-pregmod
  • bigtiddygothbf/fc-pregmod
  • Qotsafan/fc-pregmod
  • Zachpocalypse/fc-pregmod
  • milkanon66/fc-pregmod
  • GreGGoZZ/fc-pregmod
  • drsnarf86/fc-pregmod
  • valen102938/fc-pregmod
  • pregspammer/fc-pregmod
  • ponderin94/fc-pregmod
  • nook/fc-pregmod
  • carnifex34/fc-pregmod-mod-carni
  • SyntheticHigh/fc-pregmod
  • bob112211/fc-pregmod
  • amomynous0/fc-pregmod
  • oxone/fc-pregmod
  • MaxEuwe/fc-pregmod
  • nekoanon/fc-pregmod
  • preglocke/fc-pregmod
  • valen10293847/fc-pregmod
  • 2hu4u/fc-pregmod
  • mayibrad/fc-pregmod
  • Screm/fc-pregmod
  • Ansopedi/fc-pregmod
  • mrchaosbones/fc-pregmod
  • putrid/fc-pregmod
  • Kinnerman/fc-pregmod
  • gungrave1155/fc-pregmod
  • prndev/fc-pregmod
  • weresmilodon/fc-pregmod
  • auxxigobin/fc-pregmod
  • alice-chan/fc-pregmod
  • wigglie/fc-pregmod
  • jrliltfgb/fc-pregmod
  • Lord.alek.shade/fc-pregmod
  • truetailthesquire/fc-pregmod
  • lowercasedonkey/fc-pregmod
  • alice-chan9/fc-pregmod
  • eroglyphics/fc-pregmod
  • taliyent/fc-pregmod
  • zenzombie90/fc-pregmod
  • kjarik/fc-pregmod
  • wriggler/fc-pregmod
  • midnightblue/fc-pregmod
  • faraen/fc-pregmod
  • sigurd.cole/fc-pregmod
  • FCbuganon/fc-pregmod
  • kidkinster/fc-pregmod
  • Kar_Dragon/fc-pregmod
  • Zhafier/fc-pregmod
  • crcaretti/fc-pregmod
  • anond/fc-pregmod
  • tempmania/fc-pregmod
  • Dhanze/fc-pregmod
  • EstaUnCachucha/fc-pregmod
  • oniAnon/fc-pregmod
  • plebian/fc-pregmod
  • maxd569/fc-pregmod
  • Levarn/fc-pregmod
  • pumpkinspice/fc-pregmod
  • GammaXai/fc-pregmod
  • DanBackslide/fc-pregmod
  • i107760/fc-pregmod
  • Absimiliard/fc-pregmod
  • AmbrosiaCheesecake/fc-pregmod
  • fuguer/fc-pregmod
  • Azurel/fc-pregmod
  • Fake_Dev/fc-pregmod
  • ddongsanda/fc-pregmod
  • Combine456/fc-pregmod
  • UnwrappedGodiva/fc-pregmod
  • toyRuberDucky/fc-pregmod
  • zmobie/fc-pregmod
  • chuongk/fc-pregmod
  • BigWalnuts/fc-pregmod
  • Birdstrike/fc-pregmod
  • r3d/fc-pregmod
  • mawspa/fc-pregmod
  • sushila/fc-pregmod
  • DeathShip/fc-pregmod
  • eggrollsandwich/fc-pregmod
  • krayken/fc-pregmod
  • Reman/fc-pregmod
  • dwiafgts/fc-pregmod
  • jort93/fc-pregmod
  • teruterubouzu/fc-pregmod
  • flut/fc-pregmod
  • john-normal/fc-pregmod
  • Jonathan2405/fc-pregmod
  • Tyrgalon/fc-pregmod
  • NovX/fc-pregmod
  • Star1/fc-pregmod
  • Transhumanist01/fc-pregmod
  • m1017242/fc-pregmod
  • Rizal98798/fc-pregmod
  • jamezu369/fc-pregmod
  • thisisawittyname/fc-pregmod
  • KnightBoulegard/fc-pregmod
  • jblack/fc-pregmod
  • Souldrainr/fc-pregmod
  • torbjornhub/fc-pregmod
  • turnop/fc-pregmod
  • breadedpigeon/fc-pregmod
  • fire.maker/fc-pregmod
  • Inahaze/fc-pregmod
  • Waerjak/fc-pregmod
  • Trashman1138/fc-pregmod
  • supanintendo/fc-pregmod
  • _no0neman/fc-pregmod
  • Weslo/fc-pregmod
  • qw89/fc-pregmod
  • EvilDruid/fc-pregmod
  • dt25/fc-pregmod
  • Raou/fc-pregmod
  • DDouFu/fc-pregmod
  • Mauno/fc-pregmod
  • PandemoniumPenguin/fc-pregmod
  • AngelPuppet/fc-pregmod
  • DasUser79/fc-pregmod
  • Keaeag3s/fc-pregmod
  • HazeHazeHaze/fc-pregmod
  • hpotato/fc-pregmod
  • owouchthatbloodyhurt/fc-pregmod
  • v7Silent/fc-pregmod
  • nickylass/fc-pregmod
  • ThePrimer/fc-pregmod
  • PineCone/fc-pregmod
  • bruhmomentum17/fc-pregmod
  • CheatDude/fc-pregmod
  • synnove/fc-pregmod
  • en_bees/fc-pregmod
  • seronis/fc-pregmod
  • Nepidinepnep/fc-pregmod
  • Titanninja/fc-pregmod
  • Elohiem/fc-pregmod
  • cocoajazz/fc-pregmod
  • tfwncagf/fc-pregmod
  • ChunkyMonke/fc-pregmod
  • Dracoman671/fc-pregmod
  • jgl/fc-pregmod
  • Inev/fc-pregmod
  • jbige/fc-pregmod
  • MonsterMate/fc-pregmod
  • Konstantin6961/fc-pregmod
  • darth_ashi/fc-pregmod
  • shinx/fc-pregmod
  • Anu/fc-pregmod
  • Greytide/fc-pregmod
  • Bonafidemetal/fc-pregmod
  • Peje/fc-pregmod
  • Hexfy98/fc-pregmod
  • TooSlow/fc-pregmod
  • SoGu/fc-pregmod
  • CloudyCoffee/fc-pregmod
  • Welptard/fc-pregmod
  • Ploc/fc-pregmod-ploc
  • rain-/fc-pregmod
  • Pecanus/fc-pregmod
  • Jhortrax/fc-pregmod
  • valleytwo/fc-pregmod
  • QCmd/fc-pregmod
  • kung-wada/fc-pregmod
  • LolGaye/fc-pregmod
  • Exspiravit1/fc-pregmod
  • jadeddog/fc-pregmod
  • buster-scruggs/fs-antebellum-revivalism
  • policia123/fc-pregmod
  • evrgentesee/fc-pregmod
  • rko127/fc-pregmod
  • ExcalGrip12/fc-pregmod
  • BlackAion/fc-pregmod
  • Boss2020/fc-pregmod
  • Lawled/fc-pregmod
  • shiro/fc-pregmod
  • Skavenkeri/fc-pregmod
  • PooPooDooDooHead/fc-pregmod
  • Dugee/fc-pregmod
  • Portal124/fc-pregmod-vore
  • Fekenol/fc-pregmod
  • elGuapo/fc-pregmod
  • KelioSteel/fc-pregmod
  • sldlddk/fc-pregmod
  • lumepanter/fc-pregmod
  • ryuhana/fc-pregmod
  • Nene1009yb/fc-pregmod
  • DontAskDontTell/fc-pregmod-extra-events
  • Dulgi/fc-pregmod
  • Jate/fc-pregmod
  • percy365/fc-pregmod
  • franklygeorge/fc-pregmod
  • Dragneel117/fc-pregmod
  • vl96/fc-pregmod
  • Gorlom/fc-economicmod
  • NotAlive/fc-pregmod
  • Heretek/fc-pregmod
  • joeshmo828282/fc-pregmod
  • deswes/fc-pregmod
  • Nanana21/fc-pregmod
  • Gbr6/fc-pregmod
  • RandomNecro/fc-pregmod
  • Trinidad/fc-pregmod
  • anonymousey/fc-pregmod
  • macaronideath/fc-pregmod
  • fcbleh/fc-pregmod
  • jk3000/fc-pregmod
  • Akane/fc-pregmod
  • TheBoi/fc-pregmod
  • Sheenariel/fc-pregmod
  • Metapod/multi-custom
  • Banyanael/fc-pregmod
  • frogge/fc-pregmod
  • idkkk12385/fc-pregmod
  • Mirarara/fc-pregmod
  • DeaDa/fc-pregmod-thedeal
  • CobraCommander/fc-pregmod
  • bicobus/fc-pregmod
  • CardcaptorRLH85/fc-pregmod
  • temp-ui-start/fc-pregmod
  • PresidentConvert/fc-pregmod
  • delizious/fc-pregmod
  • Ducati/fc-pregmod
  • DerangedLoner/fc-pregmod-development-fork
  • ProjectVictory/fc-pregmod
  • forecastle/fc-pregmod
  • Apathy/fc-pregmod
  • indf/fc-pregmod-dev
  • GavAndAlt/fc-pregmod
  • hagamablabla/fc-pregmod
  • Alaco/fc-pregmod
  • DCoded/fc-pregmod
  • LittlePlague/fc-pregmod
  • MissOnahole/fc-pregmod
  • ishy2317/fc-pregmod
  • nielkazama/fc-pregmod
  • Phobos/fc-pregmod
  • kraster/fc-pregmod
  • JasWS/fc-pregmod
  • FelixJS/fc-pregmod
  • NCherfaoui/fc-pregmod
  • MidnightMoose/fc-pregmod
  • jjjjjj/fc-pregmod
  • Cl0ver/fc-pregmod
  • Pythoniqus/fc-pregmod
  • JohnMolotov/fc-pregmod
  • anonymouspregmodder/fc-pregmod-anonymouspregmodder
  • Fanatey/fc-pregmod
  • Mizako/fc-pregmod
  • Nithhogg/fc-pregmod
  • Bluecoffee/fc-pregmod
380 results
Show changes
Showing
with 8564 additions and 556 deletions
#pragma once
#include "fchost_storage.h"
// storage handlers that can be registered directly to Javascript to enable fchost_storage backing
class FCHostStorageHandler : public CefV8Handler
{
public:
FCHostStorageHandler(bool _persistent) : persistent(_persistent) {};
virtual bool Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) override;
private:
bool persistent;
IMPLEMENT_REFCOUNTING(FCHostStorageHandler);
};
void FCHostStorageRegister(const std::filesystem::path& persistPath, CefRefPtr<CefV8Value> object);
// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
#include <windows.h>
#include <KnownFolders.h>
#include <ShlObj.h>
#include "fchost_app.h"
std::filesystem::path FCHostApp::GetLocalStorePath()
{
PWSTR ppath;
SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &ppath);
std::filesystem::path local_storage = ppath;
CoTaskMemFree(ppath);
return local_storage / L"FreeCities_Pregmod";
}
// Entry point function for all processes.
int APIENTRY wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow) {
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// Provide CEF with command-line arguments.
CefMainArgs main_args(hInstance);
// It will create the first browser instance in OnContextInitialized() after
// CEF has initialized.
CefRefPtr<FCHostApp> app(new FCHostApp);
// CEF applications have multiple sub-processes (render, plugin, GPU, etc)
// that share the same executable. This function checks the command-line and,
// if this is a sub-process, executes the appropriate logic.
int exit_code = CefExecuteProcess(main_args, app, nullptr);
if (exit_code >= 0) {
// The sub-process has completed so return here.
return exit_code;
}
CefRefPtr<CefCommandLine> command_line = CefCommandLine::CreateCommandLine();
command_line->InitFromString(lpCmdLine);
return app->main(main_args, command_line.get());
}
FCHost/fchost/res/fchost.ico

21.9 KiB

// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by fchost.rc
//
#define IDI_FCHOST 100
// Avoid files associated with MacOS
#define _X86_
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC 1
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 32700
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 102
#endif
#endif
#include "./utility.h"
CefString cef_string_from_path(const std::filesystem::path& p)
{
return CefString(p.native());
}
void cef_string_from_path(const std::filesystem::path& p, cef_string_t* str)
{
const auto& pstr = p.native();
#if defined(OS_WIN)
cef_string_from_wide(pstr.c_str(), pstr.size(), str);
#else
cef_string_from_utf8(pstr.c_str(), pstr.size(), str);
#endif
}
#pragma once
#include "include/capi/cef_base_capi.h"
#include <filesystem>
CefString cef_string_from_path(const std::filesystem::path& p);
void cef_string_from_path(const std::filesystem::path& p, cef_string_t* str);
# The workflow for updating the SugarCube format.js file
sugarcube:
echo "Compiling SugarCube"
(cd submodules/sugarcube-2/ && node build.js -n -b 2)
mv submodules/sugarcube-2/build/twine2/sugarcube-2/format.js devTools/tweeGo/storyFormats/sugarcube-2/format.js
(cd submodules/sugarcube-2/ && git diff master fc-rebased > sugarcube-fc-changes.patch)
mv submodules/sugarcube-2/sugarcube-fc-changes.patch devNotes/"sugarcube stuff"/sugarcube-fc-changes.patch
.PHONY: sugarcube
Common problems: <!-- cSpell:ignore nwjs -->
How do I start the game? # Free Cities - pregmod
-Run the compile file, go to folder "bin", click the "FC_Pregmod" and play. (Recommendation: Drag it into incognito mode)
Pregmod is a modification of the original [Free Cities](https://freecitiesblog.blogspot.com/) created by FCdev.
I get an error on gamestart reading "Apologies! A fatal error has occurred. Aborting. Error: Unexpected token @ in JSON at position 0. Stack Trace: SyntaxError: Unexpected token @ in JSON at position 0 at JSON.parse (<anonymous>) at JSON.value" or some variant
-clear cookies ## Play the game
Everything is broken! * WARNING - FCHost users need to update or rebuild FCHost before running alpha.34 or later (or the latest build) or it WILL DELETE YOUR LOCAL SAVES!!! Download info is at https://gitgud.io/pregmodfan/fc-pregmod/-/blob/pregmod-master/FCHost/README.md?ref_type=heads#fchost and build info is at https://gitgud.io/pregmodfan/fc-pregmod/-/blob/pregmod-master/FCHost/HowToBuild.md#how-to-build
-Do not copy over your existing download as it may leave old files behind, replace it entirely
1. Download the game
I can't save more than once or twice. * [Current release](https://gitgud.io/pregmodfan/fc-pregmod/-/releases)
-Known issue caused by sugarcube level changes. Save to file doesn't have this problem and will likely avoid the first problem as well. * [Latest build](https://gitgud.io/pregmodfan/fc-pregmod/-/jobs/artifacts/pregmod-master/download?job=build)
-It is possible to increase the memory utilized by your browser to delay this 2. Open the game in your preferred browser
* On PC, we recommend either Firefox or [FCHost](FCHost/README.md).
I wish to report a sanityCheck issue. * Recommendation: Drag it into incognito mode
-Great, however a large majority of the results are false positives coming from those specific sections being split over several lines in the name of readability and git grep's intentionally (http://git.661346.n2.nabble.com/bug-git-grep-P-and-multiline-mode-td7613900.html ) lacking support for multiline. An Attempt to add -Pzl (https://gitgud.io/pregmodfan/fc-pregmod/merge_requests/2108 ) created a sub condition black hole. What follows are examples of common false positives that can safely be ignored; 3. Have fun!
[MissingClosingAngleBracket]src/art/vector/Generate_Stylesheet.tw:11:<<print "<style>."+_art_display_class+" { ### Compile the game yourself
<<print "<style>."+_art_display_class+" {
position: absolute; If you want to tweak the game a bit, you can easily download the files and compile it yourself.
height: 100%;
margin-left: auto; 1. Clone the git repository:
margin-right: auto; 1. [Install Git for terminal](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) or a Git GUI of your
left: 0; choice.
right: 0; 2. Clone the repo
} * Via terminal: `git clone --single-branch https://gitgud.io/pregmodfan/fc-pregmod.git`
3. Get updates
How to mod (basic doc): * Via terminal: `git pull`
1. All sources now in the src subdir, in separate files. 1 passage = 1 file. 2. Compile the game:
* Using one of two methods
2. Special files and dir's: 1. The simple compiler by running `simple-compiler.bat` (Windows) or `simple-compiler.sh` (Mac/Linux)
- src/config - configuration of the story is here. * Benefits:
- src/config/start.tw - contains list of .tw passage files, regenerated automatic, by building scripts. Do not change by hands. (original passage Start from pregmod renamed and moved to src/events/intro/introSummary.tw) * Requires no external dependencies
- src/js/storyJS.tw - special passage with [script] tag - contain all native JavaScript from pregmod. * Slightly faster compiling
- devTools/tweeGo/targets/sugarcube-2/userlib.js - on original FC JS moved here (I deleted it after moving JS to storyJS.tw). Compare to storyJS.tw but do not copy file here. May conflict with src/js/storyJS.tw if copied. 2. The advanced compiler by running `compile.bat` (Windows) or `compile.sh` (Mac/Linux)
- src/pregmod - I put all pregmod-only passages here. * Requires:
- .gitignore - special file for git - to ignore some files. For example - compilation results. * [Git](https://git-scm.com/)
* [Node.js](https://nodejs.org)
3. Compilation: * ~500 MB of Node packages
* `compile.[bat, sh]` will attempt to help you with the installation of its dependencies
Windows: * Benefits:
Run compile.bat - result will be file bin/FC_pregmod.html * Easier debugging
Second run of compile.but will overwrite bin/FC_pregmod.html without prompt. * Early problem detection
* Spell checking
Linux: * Tweaking of compiler settings by running `setup.bat` (Windows) or `setup.sh` (Mac/Linux)
Ensure executable permission on file "devTools/tweeGo/tweego" (not tweego.exe!) * Copies `FC_pregmod.html` to `FCHost` if it is installed
Ensure executable permission on file "compile.sh" * Live reloading of FC after file changes by running `watcher.bat` (Windows) or `watcher.sh` (Mac/Linux)
In the root dir of sources (where you see src, devTools, bin...) run command "./compile.sh" from console * We suggest using the advanced compiler when possible.
* **The second run of the compiler will overwrite the existing `FC_pregmod.html` file!**
Mac: * **All our tooling expects that you are running them in FC's root directory** (Where you see devTools, src, js, etc). Failure to do so will result in errors.
Not supported directly (I don't have access to Mac for testing). * If you are using an ARM-based device, you may need to use `arch -x86_64 bash [compile or simple-compiler].sh` to properly compile.
But you can use linux compilation script if you download tweego for mac from here: https://bitbucket.org/tmedwards/tweego/downloads/ and replace linux executable with mac executable in ./devTools/tweeGo/ folder. This is not tested though, so be warned.
3. To play open `FC_pregmod.html` in the `bin/` folder
4. Simple comparing and merging with original FC: * Repeat steps 2 and 3 after you make any changes or use `watcher.[bat, sh]` to do them automatically.
Use meld tool. Place folder FreeCities (original FC sources tree) near FreeCitiesPregmod (this sources tree) and use command: ## Common problems
meld FreeCities FreeCitiesPregmod
or just select these folders in meld's GUI. * If compiling takes a long time (more than 2 minutes) or causes a noticeable increase in system resource utilisation.
- FC's compiler makes a lot of file changes over a short period of time. Some Antivirus programs will make FC's compiler wait while it scans the contents of each changed file. So it might be worth making sure FC's directory is excluded in your Antivirus settings.
5. All modders will be very grateful if anyone who makes some changes to game with .html file also post their resulting src folder tree. * If your Antivirus is Windows Defender (currently tested with Windows 10 on 04/14/2024):
* `Start menu` -> `Windows Security` -> `Virus & threat protection` -> `Virus & threat protection settings` ->
6. For contributors to pregmod: if you don't use git, then you need to post your version of src folder tree, not just produced FC_pregmod.html file!!! This html file can't be reverted to proper sources, and useless as contribution! `Manage settings` -> `Exclusions (near the bottom)` -> `Add or remove exclusions` -> `Add an exclusion` ->
`path to FC's root directory (Where you see devTools, src, js, etc).`
7. Git workflow:
- Master branch is pregmod-master. Only Pregmodder can add something to it directly. Always contain his last public changes. * `sessionStorage quota exceeded` / `localStorage quota exceeded` or something similar
- pregmod-dev - branch with experimental code mainly by pregmodfan. - Your saves stored inside the browser are getting too large. There are multiple ways to solve this:
- Any contributions will be placed in separate branches like pregmod-mod-<something> (if it's ready to merge with master complete feature/mod) or pregmod-contrib-<something> if it's partial work until contributions is reviewed. 1. Delete saves stored in the browser. If you want to keep them, save them to disk first.
2. Disable autosave and delete the current one. Due to technical reasons autosaves are larger than normal saves, so this may help more than expected.
Typical cycle with git: 3. If on Firefox, raise the storage limit: Type `about:config` in the address bar and search for
1. Make account on gitgud if you don't have usable one. `dom.storage.default_quota`. Increase this value as needed. Default value is 5120 kilobytes / 5 MB.
2. Fork main repository through gitgud interface. (Or pull changes from main repo if you already have fork.) 4. Switch to a different browser. Recommended is either Firefox or [FCHost](FCHost/README.md), a custom HTML renderer specifically for Pregmod.
3. Clone your fork to local machine with git client (Or pull changes if already cloned.) 5. If you absolutely need to use Google Chrome:
4. Make you changes as you like, commit, and push result into your forked repository (with git client). 1. download and unzip [NW.js SDK](https://nwjs.io/downloads/) for your operative system.
5. Make merge request through gitgud interface. 2. copy the game file (FC_pregmod.html) into the `nwjs-sdk-v0.XX.Y-YOUR_OS` folder
3. in the same folder, create a text file with the following content:
```
{
"name": "Free Cities pregmod edition",
"main": "FC_pregmod.html",
"dom_storage_quota":30
}
```
and save it as package.json. In this example, 30 is the limit (in MB) that is set for the storage quota,
but you can replace it with any number. Google Chrome has the same default value as Firefox.
4. Double click nw.exe to launch the game.
* Everything is broken!
- **Do not copy over your existing download** as it may leave old files behind, replace it entirely
* I can't save more than once or twice.
- Known issue caused by SugarCube level changes. Save to file doesn't have this problem and will likely avoid the
first problem as well.
- It is possible to increase the memory utilized by your browser to delay this
* I wish to report an issue.
1. Search [issues](https://gitgud.io/pregmodfan/fc-pregmod/-/issues) to see if someone has already reported the issue.
2. [Open a new issue](https://gitgud.io/pregmodfan/fc-pregmod/-/issues/new) or, if you are interested in trying to fix it yourself, please see our guide on [contributing](CONTRIBUTING.md).
## Contribute
New Contributors are always welcome. Basic information before you start can be found [here](CONTRIBUTING.md).
## Submodules
FC uses a modified version of SugarCube 2. More information can be found [here](devNotes/sugarcube stuff/building SugarCube.md).
Further Development:
- specialized slave schools
- fortifications
- more levels for militia edict (further militarize society)
- conquering other arcologies?
Events:
- famous criminal escapes to the arcology, followed by another arcology's police force
Bugs:
- sometimes troop counts breaks
- sometimes rebel numbers have fractionary parts
Rules Assistant:
- find a way for the new intense drugs to fit in
- everything mentioned in https://gitgud.io/pregmodfan/fc-pregmod/issues/81
main.tw porting:
- slaveart
- createsimpletabs
- displaybuilding
- optionssortasappearsonmain
- resetassignmentfilter
- mainlinks
- arcology description
- office description
- slave summary
- use guard
- toychest
- walk past
<!-- cSpell:ignore faraen, eart, lxml, XMLID -->
# How to vector art # How to vector art
Please read the whole document before starting to work. Please read the whole document before starting to work.
...@@ -36,6 +38,48 @@ While editing, keep the Layers in mind. ...@@ -36,6 +38,48 @@ While editing, keep the Layers in mind.
* There are some globally available styles defined as CSS classes (e.g. skin, hair). * There are some globally available styles defined as CSS classes (e.g. skin, hair).
Use them if your asset should be changed upon display. Use them if your asset should be changed upon display.
Do not set the style directly in the object. Do not set the style directly in the object.
* You can use Inkscape to define and add classes to paths.
To add a class to a path, open the XML editor with the path selected (Edit → XML Editor...)
Delete all fields except for those with the names 'id', and 'd'.
Add a new field to the path and name it 'class'.
Make its value one of the values defined in the below section.
**MAINTAIN THESE CLASS DEFINITIONS BY MANUALLY EDITING YOUR WORKING SVG FILE**
<style
type="text/css"
id="style">
/* please maintain these definitions manually */
.white{fill:#FFFFFF;}
.skin{fill:#F6E0E8;}
.head{}
.eart{}
.torso{}
.tail{}
.boob{}
.penis{}
.scrotum{}
.areola{fill:#D76B93;}
.bellybutton{fill:#D76B93;}
.labia{fill:#D76B93;}
.hair{fill:#3F403F;}
.eyebrow_hair{fill:#3F403F;}
.shoe{fill:#3E65B0;}
.shoe_shadow{fill:#15406D;}
.smart_piercing{fill:#4DB748;}
.steel_piercing{fill:#787878;}
.steel_chastity{fill:#BABABA;}
.gag{fill:#BF2126;}
.shadow{fill:#010101;}
.glasses{}
.eye{}
.sclera{fill:#FFFFFF;}
.lips{}
</style>
You should be able to find this structure somewhere in the first 500 lines of your working svg file.
The formatting of these definitions matters for `normalize_svg.py`, which is invoked as part of `vector_layer_split.py` and explained below.
## 3. Normalize the document (before committing) ## 3. Normalize the document (before committing)
...@@ -48,7 +92,7 @@ Use ...@@ -48,7 +92,7 @@ Use
before committing to normalize the format so git will not freak out about the changed indentation. before committing to normalize the format so git will not freak out about the changed indentation.
If you use Inkscape, please use in Edit → Settings → Input/Output → SVG-Output → Path Data → Optimized. This is the default. If you use Inkscape, please use Edit → Settings → Input/Output → SVG-Output → Path Data → Optimized. This is the default.
In case your Editor uses another path data style which cannot be changed, please contact the other artists and developers via the issue tracker to find a new common ground. In case your Editor uses another path data style which cannot be changed, please contact the other artists and developers via the issue tracker to find a new common ground.
What it does: What it does:
......
This diff is collapsed.
#!/usr/bin/env python3 #!/usr/bin/env python3
''' """
Application for "normalizing" SVGs Application for "normalizing" SVGs
These problems are addressed: These problems are addressed:
...@@ -13,75 +13,125 @@ https://bugs.launchpad.net/inkscape/+bug/167937 ...@@ -13,75 +13,125 @@ https://bugs.launchpad.net/inkscape/+bug/167937
Usage Example: Usage Example:
python3 inkscape_svg_fixup.py vector_source.svg python3 inkscape_svg_fixup.py vector_source.svg
''' """
import lxml.etree as etree import re
import sys import sys
import lxml.etree as etree
color_classes = {
'skin', 'head', 'torso', 'boob', 'penis', 'scrotum', 'belly', 'areola', 'bellybutton', 'labia', 'hair',
'pubic_hair', 'armpit_hair', 'eyebrow_hair', 'shoe', 'shoe_shadow', 'smart_piercing', 'steel_piercing',
'steel_chastity', 'outfit_base', 'gag', 'shadow', 'glasses', 'eye', 'sclera', 'eart', 'tail',
'white', 'skin', 'skin_highlight', 'skin_shade', 'skin_strong_highlight', 'skin_strong_shade', 'arm',
'arm_highlight', 'arm_shade', 'head', 'head_highlight', 'head_shade', 'torso', 'torso_highlight',
'torso_shade', 'boob', 'boob_highlight', 'boob_shade', 'penis', 'penis_highlight', 'penis_shade',
'scrotum', 'scrotum_highlight', 'scrotum_shade', 'belly', 'belly_highlight', 'belly_shade', 'neck',
'neck_highlight', 'neck_shade', 'legs', 'legs_highlight', 'legs_shade', 'butt', 'butt_highlight',
'butt_shade', 'feet', 'feet_highlight', 'feet_shade', 'areola', 'labia', 'hair', 'shoe_shadow',
'smart_piercing', 'steel_piercing', 'steel_chastity', 'gag', 'shadow', 'glasses', 'lips', 'eyeball',
'iris', 'highlight1', 'highlight2', 'highlight3', 'highlightStrong', 'armpit_hair', 'pubic_hair',
'muscle_tone', 'belly_details', 'shoe_primary', 'shoe_primary_highlight', 'shoe_primary_shade',
'shoe_accent', 'shoe_accent_highlight', 'shoe_accent_shade', 'top_primary', 'top_primary_highlight',
'top_primary_shade', 'top_accent', 'top_accent_highlight', 'top_accent_shade', 'bottoms_primary',
'bottoms_primary_highlight', 'bottoms_primary_shade', 'bottoms_accent', 'bottoms_accent_highlight',
'bottoms_accent_shade', 'bra_primary', 'bra_primary_highlight', 'bra_primary_shade', 'bra_accent',
'bra_accent_highlight', 'bra_accent_shade', 'bra_strap1', 'bra_strap2', 'bra_strap3', 'shirt_center1',
'shirt_center2', 'shirt_center3', 'panties_primary', 'panties_primary_highlight', 'panties_primary_shade',
'panties_accent', 'panties_accent_highlight', 'panties_accent_shade', 'stockings_primary',
'stockings_accent', 'top_primary_strong_highlight', 'top_primary_strong_shade',
'top_accent_strong_highlight', 'top_accent_strong_shade', 'bottoms_primary_strong_highlight',
'bottoms_primary_strong_shade', 'bottoms_accent_strong_highlight', 'bottoms_accent_strong_shade',
'bellymask_1', 'bellymask_2', 'bellymask_3', 'bellymask_4', 'bellymask_5', 'bellymask_6', 'bellymask_7',
'bellymask_8', 'bellymask_9', 'bellymask_normal', 'bellymask_hourglass', 'bellymask_unnatural', "feet_nails"
}
def fix(tree): def fix(tree):
# know namespaces # know namespaces
ns = { ns = {
'svg' : 'http://www.w3.org/2000/svg', 'svg': 'http://www.w3.org/2000/svg',
'inkscape' : 'http://www.inkscape.org/namespaces/inkscape' 'inkscape': 'http://www.inkscape.org/namespaces/inkscape'
} }
# find document global style definition # find document global style definition
# mangle it and interpret as python dictionary # mangle it and interpret as python dictionary
style_element = tree.find('./svg:style',namespaces=ns) style_element = tree.find('./svg:style', namespaces=ns)
style_definitions = style_element.text style_definitions = style_element.text
pythonic_style_definitions = '{'+style_definitions.\ pythonic_style_definitions = '{' + style_definitions. \
replace('\t.','"').replace('{','":"').replace('}','",').\ replace('\t.', '"').replace('{', '":"').replace('}', '",'). \
replace('/*','#')+'}' replace('/*', '#') + '}'
styles = eval(pythonic_style_definitions) styles = eval(pythonic_style_definitions)
# go through all SVG elements # go through all SVG elements
for elem in tree.iter(): for elem in tree.iter():
if (elem.tag == etree.QName(ns['svg'], 'g')): if elem.tag == etree.QName(ns['svg'], 'g'):
# compare inkscape label with group element ID # compare inkscape label with group element ID
l = elem.get(etree.QName(ns['inkscape'], 'label')) lbl = elem.get(etree.QName(ns['inkscape'], 'label'))
if l: if lbl:
i = elem.get('id') i = elem.get('id')
if (i != l): if i != lbl:
print("Overwriting ID %s with Label %s..."%(i, l)) print("Overwriting ID %s with Label %s..." % (i, lbl))
elem.set('id', l) elem.set('id', lbl)
# clean styles (for easier manual merging)
style_string = elem.get('style')
if style_string:
split_styles = style_string.strip('; ').split(';')
styles_pairs = [s.strip('; ').split(':') for s in split_styles]
filtered_pairs = [(k, v) for k, v in styles_pairs if not (
k.startswith('font-') or
k.startswith('text-') or
k.endswith('-spacing') or
k in ["line-height", " direction", " writing", " baseline-shift", " white-space", " writing-mode"]
)]
split_styles = [':'.join(p) for p in filtered_pairs]
style_string = ';'.join(sorted(split_styles))
elem.attrib["style"] = style_string
# clean styles (for easier manual merging) # remove all style attributes offending class styles
style_string = elem.get('style') s = elem.get('style')
if style_string: c = elem.get('class')
split_styles = style_string.strip('; ').split(';') if c and s:
styles_pairs = [s.strip('; ').split(':') for s in split_styles] if 'i' in locals():
filtered_pairs = [ (k,v) for k,v in styles_pairs if not ( s = s.lower()
k.startswith('font-') or c = c.split(' ')[0] # regard main style only
k.startswith('text-') or classes = c.split(' ')
k.endswith('-spacing') or has_color_class = any(x in color_classes for x in classes)
k in ["line-height", " direction", " writing", " baseline-shift", " white-space", " writing-mode"] if has_color_class:
)] s_new = re.sub('fill:#[0-9a-f]+;?', '', s)
split_styles = [':'.join(p) for p in filtered_pairs] if s != s_new:
style_string = ';'.join(sorted(split_styles)) print("Explicit fill was removed from style string ({0}) for element with ID {1} "
elem.attrib["style"] = style_string "because its class ({2}) controls the fill color".format(s, i, c))
s = s_new
if s == 'style=""': # the style is empty now
del elem.attrib["style"]
continue
cs = ''
if c in styles:
cs = styles[c].strip('; ').lower()
if c not in styles:
print("Object id %s references unknown style class %s." % (i, c))
else:
if cs != s.strip('; '):
print("Style %s removed from object id %s differed from class %s style %s." % (s, i, c, cs))
del elem.attrib["style"]
else:
print('--------------------------- i not defined - propably a clip-path with a style ---------------------------')
print("Element tag: ".format(elem.tag))
print("Element style: ".format(s))
print("Element class: ".format(c))
print(etree.tostring(elem, pretty_print=True))
print('--------------------------- i not defined - propably a clip-path with a style ---------------------------')
# remove explicit fill color if element class is one of the color_classes
# remove all style attributes offending class styles
s = elem.get('style')
c = elem.get('class')
if (c and s):
s = s.lower()
c = c.split(' ')[0] # regard main style only
cs = ''
if c in styles:
cs = styles[c].strip('; ').lower()
if (c not in styles):
print("Object id %s references unknown style class %s."%(i,c))
else:
if (cs != s.strip('; ')):
print("Style %s removed from object id %s differed from class %s style %s."%(s,i,c,cs))
del elem.attrib["style"]
if __name__ == "__main__": if __name__ == "__main__":
input_file = sys.argv[1] input_file = sys.argv[1]
tree = etree.parse(input_file) tree = etree.parse(input_file)
fix(tree) fix(tree)
# store SVG into file (input file is overwritten) # store SVG into file (input file is overwritten)
svg = etree.tostring(tree, pretty_print=True) svg = etree.tostring(tree, pretty_print=True)
with open(input_file, 'wb') as f: with open(input_file, 'wb') as f:
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8")) f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg) f.write(svg)
import os
import subprocess
import sys
if os.name == "nt":
minifier = "../devTools/minify/minify_win_amd64.exe"
elif sys.platform == "darwin":
minifier = "../devTools/minify/minify_darwin_amd64"
else:
# probably linux, if not we don't have an alternative anyways.
minifier = "../devTools/minify/minify_linux_amd64"
def save(layer, prefix, output_directory, output_format, svg_data):
i = layer.get('id')
output_path = os.path.join(output_directory, "{0}{1}.svg".format(prefix, i))
if output_format == 'svg':
with open(output_path, 'wb') as f:
# Header for normal SVG (XML)
f.write(f'<?xml version="1.0" encoding="UTF-8" standalone="no"?>{os.linesep}'.encode("utf-8"))
f.write(svg_data)
elif output_format == 'tw':
# send to minifier through stdin, which then saves to file
subprocess.run([minifier, "--mime=image/svg+xml", "-o", output_path],
input=svg_data)
#!/usr/bin/env python3 #!/usr/bin/env python3
''' """
Application for procedural content adaption Application for procedural content adaption
*THIS IS VERY EXPERIMENTAL* *THIS IS VERY EXPERIMENTAL*
Contains a very poor man's implementation of spline mesh warping. Contains a very poor man's implementation of spline mesh warping.
...@@ -23,15 +23,16 @@ python3 vector_clothing_replicator.py infile clothing bodypart destinationfile ...@@ -23,15 +23,16 @@ python3 vector_clothing_replicator.py infile clothing bodypart destinationfile
Usage Example: Usage Example:
python3 vector_clothing_replicator.py vector_source.svg Straps Boob vector_destination.svg python3 vector_clothing_replicator.py vector_source.svg Straps Boob vector_destination.svg
python3 vector_clothing_replicator.py vector_source.svg Straps Torso vector_destination.svg python3 vector_clothing_replicator.py vector_source.svg Straps Torso vector_destination.svg
''' """
from svg.path import parse_path
import copy import copy
import lxml.etree as etree
import sys import sys
import lxml.etree as etree
from svg.path import parse_path
REFERENCE_PATH_SAMPLES = 200 REFERENCE_PATH_SAMPLES = 200
EMBED_REPLICATIONS = True # whether to embed all replications into the input file or output separate files EMBED_REPLICATIONS = True # whether to embed all replications into the input file or output separate files
input_file = sys.argv[1] input_file = sys.argv[1]
clothing = sys.argv[2] clothing = sys.argv[2]
...@@ -39,121 +40,127 @@ bodypart = sys.argv[3] ...@@ -39,121 +40,127 @@ bodypart = sys.argv[3]
output_file_embed = sys.argv[4] output_file_embed = sys.argv[4]
# TODO: make these configurable # TODO: make these configurable
output_file_pattern = '%s_%s_%s.svg' #bodypart, target_id, clothing output_file_pattern = '%s_%s_%s.svg' # bodypart, target_id, clothing
if ('Torso' == bodypart): if 'Torso' == bodypart:
xpath_shape = './svg:g[@id="Torso_"]/svg:g[@id="Torso_%s"]/svg:path[@class="skin torso"]/@d' # TODO: formulate more general, independent of style xpath_shape = './svg:g[@id="Torso_"]/svg:g[@id="Torso_%s"]/svg:path[@class="skin torso"]/@d' # TODO: formulate more general, independent of style
xpath_outfit_container = '//svg:g[@id="Torso_Outfit_%s_"]'%(clothing) xpath_outfit_container = '//svg:g[@id="Torso_Outfit_%s_"]' % (clothing)
xpath_outfit = '//svg:g[@id="Torso_Outfit_%s_%s"]'%(clothing,'%s') xpath_outfit = '//svg:g[@id="Torso_Outfit_%s_%s"]' % (clothing, '%s')
target_ids = "Unnatural,Hourglass,Normal".split(",") target_ids = "Unnatural,Hourglass,Normal".split(",")
reference_id = "Hourglass" reference_id = "Hourglass"
else: else:
raise RuntimeError("Please specify a bodypart for clothing to replicate.") raise RuntimeError("Please specify a bodypart for clothing to replicate.")
tree = etree.parse(input_file) tree = etree.parse(input_file)
ns = {'svg' : 'http://www.w3.org/2000/svg'} ns = {'svg': 'http://www.w3.org/2000/svg'}
canvas = copy.deepcopy(tree) canvas = copy.deepcopy(tree)
for e in canvas.xpath('./svg:g',namespaces=ns)+canvas.xpath('./svg:path',namespaces=ns): for e in canvas.xpath('./svg:g', namespaces=ns) + canvas.xpath('./svg:path', namespaces=ns):
# TODO: this should be "remove all objects, preserve document properties" # TODO: this should be "remove all objects, preserve document properties"
e.getparent().remove(e) e.getparent().remove(e)
def get_points(xpath_shape): def get_points(xpath_shape):
''' """
This function extracts reference paths by the given xpath selector. This function extracts reference paths by the given xpath selector.
Each path is used to sample a fixed number of points. Each path is used to sample a fixed number of points.
''' """
paths_data = tree.xpath(xpath_shape,namespaces=ns) paths_data = tree.xpath(xpath_shape, namespaces=ns)
points = [] points = []
path_length = None path_length = None
for path_data in paths_data: for path_data in paths_data:
p = parse_path(path_data) p = parse_path(path_data)
points += [ points += [
p.point(1.0/float(REFERENCE_PATH_SAMPLES)*i) p.point(1.0 / float(REFERENCE_PATH_SAMPLES) * i)
for i in range(REFERENCE_PATH_SAMPLES) for i in range(REFERENCE_PATH_SAMPLES)
] ]
if (not points): if not points:
raise RuntimeError( raise RuntimeError(
'No paths for reference points found by selector "%s".'%(xpath_shape) 'No paths for reference points found by selector "%s".' % xpath_shape
) )
return points return points
def point_movement(point, reference_points, target_points): def point_movement(point, reference_points, target_points):
''' """
For a given point, finds the nearest point in the reference path. For a given point, finds the nearest point in the reference path.
Gives distance vector from the nearest reference point to the Gives distance vector from the nearest reference point to the
respective target reference point. respective target reference point.
''' """
distances = [abs(point-reference_point) for reference_point in reference_points] distances = [abs(point - reference_point) for reference_point in reference_points]
min_ref_dist_idx = min(enumerate(distances), key=lambda x:x[1])[0] min_ref_dist_idx = min(enumerate(distances), key=lambda x: x[1])[0]
movement = target_points[min_ref_dist_idx] - reference_points[min_ref_dist_idx] movement = target_points[min_ref_dist_idx] - reference_points[min_ref_dist_idx]
return movement return movement
reference_points = get_points(xpath_shape%(reference_id))
container = tree.xpath(xpath_outfit_container,namespaces=ns) reference_points = get_points(xpath_shape % reference_id)
if (len(container) != 1): container = tree.xpath(xpath_outfit_container, namespaces=ns)
raise RuntimeError('Outfit container selector "%s" does not yield exactly one layer.'%(xpath_outfit_container)) if len(container) != 1:
raise RuntimeError('Outfit container selector "%s" does not yield exactly one layer.' % xpath_outfit_container)
container = container[0] container = container[0]
outfit_source = container.xpath(xpath_outfit%(reference_id),namespaces=ns) outfit_source = container.xpath(xpath_outfit % reference_id, namespaces=ns)
if (len(outfit_source) != 1): if len(outfit_source) != 1:
raise RuntimeError('Outfit source selector "%s" does not yield exactly one outfit layer in container selected by "%s".'%(xpath_outfit%(reference_id), xpath_outfit_container)) raise RuntimeError(
'Outfit source selector "%s" does not yield exactly one outfit layer in container selected by "%s".' % (
xpath_outfit % reference_id, xpath_outfit_container))
outfit_source = outfit_source[0] outfit_source = outfit_source[0]
for target_id in target_ids: for target_id in target_ids:
print( print(
'Generating variant "%s" of clothing "%s" for bodypart "%s"...'% 'Generating variant "%s" of clothing "%s" for bodypart "%s"...' %
(target_id, clothing, bodypart) (target_id, clothing, bodypart)
) )
outfit = copy.deepcopy(outfit_source) outfit = copy.deepcopy(outfit_source)
paths = outfit.xpath('./svg:path',namespaces=ns) paths = outfit.xpath('./svg:path', namespaces=ns)
if target_id == reference_id: if target_id == reference_id:
print("This is the source variant. Skipping...") print("This is the source variant. Skipping...")
else: else:
layerid = outfit.get('id').replace('_%s'%(reference_id),'_%s'%(target_id)) layerid = outfit.get('id').replace('_%s' % reference_id, '_%s' % target_id)
outfit.set('id', layerid) outfit.set('id', layerid)
outfit.set(etree.QName('http://www.inkscape.org/namespaces/inkscape', 'label'), layerid) # for the Inkscape-users outfit.set(etree.QName('http://www.inkscape.org/namespaces/inkscape', 'label'),
target_points = get_points(xpath_shape%(target_id)) layerid) # for the Inkscape-users
if (len(reference_points) != len(target_points)): target_points = get_points(xpath_shape % target_id)
raise RuntimeError( if len(reference_points) != len(target_points):
('Different amounts of sampled points in reference "%s" and target "%s" paths. '+ raise RuntimeError(
'Selector "%s" probably matches different number of paths in the two layers.')% ('Different amounts of sampled points in reference "%s" and target "%s" paths. ' +
(reference_id, target_id, xpath_shape) 'Selector "%s" probably matches different number of paths in the two layers.') %
) (reference_id, target_id, xpath_shape)
for path in paths: )
path_data = path.get("d") for path in paths:
p = parse_path(path_data) path_data = path.get("d")
for segment in p: p = parse_path(path_data)
original_distance = abs(segment.end-segment.start) for segment in p:
start_movement = point_movement(segment.start, reference_points, target_points) original_distance = abs(segment.end - segment.start)
segment.start += start_movement start_movement = point_movement(segment.start, reference_points, target_points)
end_movement = point_movement(segment.end, reference_points, target_points) segment.start += start_movement
segment.end += end_movement end_movement = point_movement(segment.end, reference_points, target_points)
distance = abs(segment.end-segment.start) segment.end += end_movement
try: distance = abs(segment.end - segment.start)
# enhance position of CubicBezier control points try:
# amplification is relative to the distance gained by movement # enhance position of CubicBezier control points
segment.control1 += start_movement # amplification is relative to the distance gained by movement
segment.control1 += (segment.control1-segment.start)*(distance/original_distance-1.0) segment.control1 += start_movement
segment.control2 += end_movement segment.control1 += (segment.control1 - segment.start) * (distance / original_distance - 1.0)
segment.control2 += (segment.control2-segment.end)*(distance/original_distance-1.0) segment.control2 += end_movement
except AttributeError as ae: segment.control2 += (segment.control2 - segment.end) * (distance / original_distance - 1.0)
# segment is not a CubicBezier except AttributeError as ae:
pass # segment is not a CubicBezier
path.set("d", p.d()) pass
if EMBED_REPLICATIONS: path.set("d", p.d())
container.append(outfit) if EMBED_REPLICATIONS:
if not EMBED_REPLICATIONS: container.append(outfit)
container = copy.deepcopy(canvas).xpath('.',namespaces=ns)[0] if not EMBED_REPLICATIONS:
container.append(outfit) container = copy.deepcopy(canvas).xpath('.', namespaces=ns)[0]
container.append(outfit)
if not EMBED_REPLICATIONS:
svg = etree.tostring(container, pretty_print=True) if not EMBED_REPLICATIONS:
with open((output_file_pattern%(bodypart, target_id, clothing)).lower(), 'wb') as f: svg = etree.tostring(container, pretty_print=True)
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8")) with open((output_file_pattern % (bodypart, target_id, clothing)).lower(), 'wb') as f:
f.write(svg) f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg)
if EMBED_REPLICATIONS: if EMBED_REPLICATIONS:
svg = etree.tostring(tree, pretty_print=True) svg = etree.tostring(tree, pretty_print=True)
with open(output_file_embed, 'wb') as f: with open(output_file_embed, 'wb') as f:
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8")) f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg) f.write(svg)
This diff is collapsed.
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
<inkscape:grid type="xygrid" id="grid897"/> <inkscape:grid type="xygrid" id="grid897"/>
</sodipodi:namedview> </sodipodi:namedview>
<style type="text/css" id="style"> <style type="text/css" id="style">
/* please maintain these definitions manually */ /* please maintain these definitions manually */
.white{fill:#FFFFFF;} .white{fill:#FFFFFF;}
.skin{fill:#F6E0E8;} .skin{fill:#F6E0E8;}
.head{} .head{}
This diff is collapsed.
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
<inkscape:grid type="xygrid" id="grid897"/> <inkscape:grid type="xygrid" id="grid897"/>
</sodipodi:namedview> </sodipodi:namedview>
<style type="text/css" id="style"> <style type="text/css" id="style">
/* please maintain these definitions manually */ /* please maintain these definitions manually */
.white{fill:#FFFFFF;} .white{fill:#FFFFFF;}
.skin{fill:#F6E0E8;} .skin{fill:#F6E0E8;}
.head{} .head{}
#!/usr/bin/env python3 #!/usr/bin/env python3
''' """
Application for splitting groups from one SVG file into separate files Application for splitting groups from one SVG file into separate files
Usage: Usage:
...@@ -8,117 +8,128 @@ python3 vector_layer_split.py infile format outdir ...@@ -8,117 +8,128 @@ python3 vector_layer_split.py infile format outdir
Usage Example: Usage Example:
python3 vector_layer_split.py vector_source.svg tw ../src/art/vector/layers/ python3 vector_layer_split.py vector_source.svg tw ../src/art/vector/layers/
''' """
import lxml.etree as etree import argparse
import sys
import os
import copy import copy
import os
import re import re
import lxml.etree as etree
import normalize_svg import normalize_svg
import svg_split_utils
parser = argparse.ArgumentParser(
description='Application for splitting groups from one SVG file into separate files.')
parser.add_argument('-o', '--output', dest='output_dir', required=True,
help='output directory')
parser.add_argument('-f', '--format', dest='output_format',
choices=['svg', 'tw'], default='svg', help='output format.')
parser.add_argument('-p', '--prefix', dest='prefix', default='',
help='Prepend this string to result file names')
parser.add_argument('input_file', metavar='FILENAME', nargs='+',
help='Input SVG file with layers')
args = parser.parse_args()
output_format = args.output_format
output_directory = args.output_dir
def split_file(input_file):
tree = etree.parse(input_file)
normalize_svg.fix(tree)
ns = {
'svg': 'http://www.w3.org/2000/svg',
'inkscape': 'http://www.inkscape.org/namespaces/inkscape',
'sodipodi': "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
}
# strip undesired editor attributes from the tree
etree.strip_attributes(tree, f"{{{ns['inkscape']}}}*", f"{{{ns['sodipodi']}}}*");
# prepare output template
template = copy.deepcopy(tree)
root = template.getroot()
# remove all svg root attributes except document size
for a in root.attrib:
if a != "viewBox":
del root.attrib[a]
# remove all content, including metadata
# for twine output, style definitions are removed, too
defs = None
for e in root:
if e.tag == etree.QName(ns['svg'], 'defs'):
defs = e
if (e.tag == etree.QName(ns['svg'], 'g') or
e.tag == etree.QName(ns['svg'], 'metadata') or
e.tag == etree.QName(ns['svg'], 'defs') or
e.tag == etree.QName(ns['sodipodi'], 'namedview') or
(output_format == 'tw' and e.tag == etree.QName(ns['svg'], 'style'))
):
root.remove(e)
# template preparation finished
# prepare regex for later use
regex_xmlns = re.compile(' xmlns[^ ]+')
regex_space = re.compile(r'[>]\s+[<]')
# find all groups
layers = tree.xpath('//svg:g', namespaces=ns)
for layer in layers:
i = layer.get('id')
if ( # disregard non-content groups
i.endswith("_") or # manually suppressed with underscore
i.startswith("XMLID") or # Illustrator generated group
i.startswith("g") # Inkscape generated group
):
continue
# create new canvas
output = copy.deepcopy(template)
# copy all shapes into template
canvas = output.getroot()
for e in layer:
canvas.append(e)
# represent template as SVG (binary string)
svg = etree.tostring(output, pretty_print=False)
# poor man's conditional defs insertion
# TODO: extract only referenced defs (filters, gradients, ...)
# TODO: detect necessity by traversing the elements. do not stupidly search in the string representation
if "filter:" in svg.decode('utf-8'):
# it seems there is a filter referenced in the generated SVG, re-insert defs from main document
canvas.insert(0, defs)
# re-generate output
svg = etree.tostring(output, pretty_print=False)
if output_format == 'tw':
# remove unnecessary attributes
# TODO: never generate unnecessary attributes in the first place
svg = svg.decode('utf-8')
svg = regex_xmlns.sub('', svg)
svg = svg.replace('\n', '').replace('\r', '') # print cannot be multi-line
svg = regex_space.sub('><', svg) # remove indentation
svg = svg.replace('svg:', '') # svg namespace was removed
if "Boob" in i: # internal groups are used for scaling
svg = svg.replace('<g ', '<g data-transform="boob" ') # boob art uses the boob scaling
elif "Belly" in i:
svg = svg.replace('<g ', '<g data-transform="belly" ') # belly art uses the belly scaling
elif "Balls" in i:
svg = svg.replace('<g ', '<g data-transform="balls" ') # balls art uses the balls scaling
else:
svg = svg.replace('<g ', '<g data-transform="art" ') # otherwise use default scaling
if not svg.endswith(os.linesep):
svg += os.linesep
svg = svg.encode('utf-8')
# save SVG string to file
svg_split_utils.save(layer, args.prefix, output_directory, output_format, svg)
input_file = sys.argv[1]
output_format = sys.argv[2]
output_directory = sys.argv[3]
if not os.path.exists(output_directory): if not os.path.exists(output_directory):
os.makedirs(output_directory) os.makedirs(output_directory)
ns = { for f in args.input_file:
'svg' : 'http://www.w3.org/2000/svg', split_file(f)
'inkscape' : 'http://www.inkscape.org/namespaces/inkscape',
'sodipodi':"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
}
tree = etree.parse(input_file)
normalize_svg.fix(tree)
# prepare output template
template = copy.deepcopy(tree)
root = template.getroot()
# remove all svg root attributes except document size
for a in root.attrib:
if (a != "viewBox"):
del root.attrib[a]
# add placeholder for CSS class (needed for workaround for non HTML 5.1 compliant browser)
if output_format == 'tw':
root.attrib["class"] = "'+_art_display_class+'"
# remove all content, including metadata
# for twine output, style definitions are removed, too
defs = None
for e in root:
if (e.tag == etree.QName(ns['svg'], 'defs')):
defs = e
if (e.tag == etree.QName(ns['svg'], 'g') or
e.tag == etree.QName(ns['svg'], 'metadata') or
e.tag == etree.QName(ns['svg'], 'defs') or
e.tag == etree.QName(ns['sodipodi'], 'namedview') or
(output_format == 'tw' and e.tag == etree.QName(ns['svg'], 'style'))
):
root.remove(e)
# template preparation finished
# prepare regex for later use
regex_xmlns = re.compile(' xmlns[^ ]+',)
regex_space = re.compile('[>][ ]+[<]',)
# find all groups
layers = tree.xpath('//svg:g',namespaces=ns)
for layer in layers:
i = layer.get('id')
if ( # disregard non-content groups
i.endswith("_") or # manually suppressed with underscore
i.startswith("XMLID") or # Illustrator generated group
i.startswith("g") # Inkscape generated group
):
continue
# create new canvas
output = copy.deepcopy(template)
# copy all shapes into template
canvas = output.getroot()
for e in layer:
canvas.append(e)
# represent template as SVG (binary string)
svg = etree.tostring(output, pretty_print=False)
# poor man's conditional defs insertion
# TODO: extract only referenced defs (filters, gradients, ...)
# TODO: detect necessity by traversing the elements. do not stupidly search in the string representation
if ("filter:" in svg.decode('utf-8')):
# it seems there is a filter referenced in the generated SVG, re-insert defs from main document
canvas.insert(0,defs)
# re-generate output
svg = etree.tostring(output, pretty_print=False)
if (output_format == 'tw'):
# remove unnecessary attributes
# TODO: never generate unnecessary attributes in the first place
svg = svg.decode('utf-8')
svg = regex_xmlns.sub('',svg)
svg = svg.replace(' inkscape:connector-curvature="0"','') # this just saves space
svg = svg.replace('\n','').replace('\r','') # print cannot be multi-line
svg = regex_space.sub('><',svg) # remove indentation
svg = svg.replace('svg:','') # svg namespace was removed
if ("Boob" in i): # internal groups are used for scaling
svg = svg.replace('<g ','<g transform="\'+_artTransformBoob+\'"') # boob art uses the boob scaling
elif ("Belly" in i):
svg = svg.replace('<g ','<g transform="\'+_artTransformBelly+\'"') # belly art uses the belly scaling
else:
svg = svg.replace('<g ','<g transform="\'+_art_transform+\'"') # otherwise use default scaling
svg = svg.encode('utf-8')
# save SVG string to file
i = layer.get('id')
output_path = os.path.join(output_directory,i+'.'+output_format)
with open(output_path, 'wb') as f:
if (output_format == 'svg'):
# Header for normal SVG (XML)
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg)
elif (output_format == 'tw'):
# Header for SVG in Twine file (SugarCube print statement)
f.write((':: Art_Vector_%s [nobr]\n\n'%(i)).encode("utf-8"))
f.write("<<print '<html>".encode("utf-8"))
f.write(svg)
f.write("</html>' >>".encode("utf-8"))
#!/usr/bin/env python3 #!/usr/bin/env python3
''' """
Application for splitting groups from one SVG file into separate files Application for splitting groups from one SVG file into separate files
Usage: Usage:
...@@ -8,133 +8,169 @@ python3 vector_layer_split.py infile format outdir ...@@ -8,133 +8,169 @@ python3 vector_layer_split.py infile format outdir
Usage Example: Usage Example:
python3 vector_layer_split.py vector_source.svg tw ../src/art/vector/layers/ python3 vector_layer_split.py vector_source.svg tw ../src/art/vector/layers/
''' """
import lxml.etree as etree import argparse
from lxml.etree import XMLParser, parse
import sys
import os
import copy import copy
import os
import re import re
import lxml.etree as etree
import normalize_svg import normalize_svg
from lxml.etree import XMLParser, parse
import svg_split_utils
parser = argparse.ArgumentParser(
description='Application for splitting groups from one SVG file into separate files.')
parser.add_argument('-o', '--output', dest='output_dir', required=True,
help='output directory')
parser.add_argument('-f', '--format', dest='output_format',
choices=['svg', 'tw'], default='svg', help='output format.')
parser.add_argument('-p', '--prefix', dest='prefix', default='',
help='Prepend this string to result file names')
parser.add_argument('input_file', metavar='FILENAME', nargs=1,
help='Input SVG file with layers')
input_file = sys.argv[1] args = parser.parse_args()
output_format = sys.argv[2]
output_directory = sys.argv[3] output_format = args.output_format
output_directory = args.output_dir
input_file = args.input_file[0]
if not os.path.exists(output_directory): if not os.path.exists(output_directory):
os.makedirs(output_directory) os.makedirs(output_directory)
ns = { ns = {
'svg': 'http://www.w3.org/2000/svg', 'svg': 'http://www.w3.org/2000/svg',
'inkscape': 'http://www.inkscape.org/namespaces/inkscape', 'inkscape': 'http://www.inkscape.org/namespaces/inkscape',
'sodipodi': "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd", 'sodipodi': "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
} }
p = XMLParser(huge_tree=True) p = XMLParser(huge_tree=True)
tree = parse(input_file, parser=p) tree = parse(input_file, parser=p)
#tree = etree.parse(input_file)
normalize_svg.fix(tree) normalize_svg.fix(tree)
# strip undesired editor attributes from the tree
etree.strip_attributes(tree, f"{{{ns['inkscape']}}}*", f"{{{ns['sodipodi']}}}*")
# prepare output template # prepare output template
template = copy.deepcopy(tree) template = copy.deepcopy(tree)
root = template.getroot() root = template.getroot()
# remove all svg root attributes except document size # remove all svg root attributes except document size
for a in root.attrib: for a in root.attrib:
if (a != "viewBox"): if a != "viewBox":
del root.attrib[a] del root.attrib[a]
# add placeholder for CSS class (needed for workaround for non HTML 5.1 compliant browser)
if output_format == 'tw':
root.attrib["class"] = "'+_art_display_class+'"
# remove all content, including metadata # remove all content, including metadata
# for twine output, style definitions are removed, too # for twine output, style definitions are removed, too
defs = None defs = None
for e in root: for e in root:
if (e.tag == etree.QName(ns['svg'], 'defs')): if e.tag == etree.QName(ns['svg'], 'defs'):
defs = e defs = e
if (e.tag == etree.QName(ns['svg'], 'g') or if (e.tag == etree.QName(ns['svg'], 'g') or
e.tag == etree.QName(ns['svg'], 'metadata') or e.tag == etree.QName(ns['svg'], 'metadata') or
e.tag == etree.QName(ns['svg'], 'defs') or e.tag == etree.QName(ns['svg'], 'defs') or
e.tag == etree.QName(ns['sodipodi'], 'namedview') or e.tag == etree.QName(ns['sodipodi'], 'namedview') or
(output_format == 'tw' and e.tag == etree.QName(ns['svg'], 'style')) (output_format == 'tw' and e.tag == etree.QName(ns['svg'], 'style'))
): ):
root.remove(e) root.remove(e)
# template preparation finished # template preparation finished
# prepare regex for later use # prepare regex for later use
regex_xmlns = re.compile(' xmlns[^ ]+', ) regex_xmlns = re.compile(' xmlns[^ ]+')
regex_space = re.compile('[>][ ]+[<]', ) regex_space = re.compile(r'[>]\s+[<]')
regex_transformValue = re.compile('(?<=transformVariableName=")[^"]*', )
regex_transformVar = re.compile('transformVariableName="[^"]*"', ) # create dict with all needed definitions
regex_transformAttr = re.compile('transform="[^"]*"', ) gra_dict = {}
# find all groups for gra in defs:
if (gra.tag == etree.QName(ns['svg'], 'linearGradient') or
gra.tag == etree.QName(ns['svg'], 'radialGradient') or
gra.tag == etree.QName(ns['svg'], 'clipPath') or
gra.tag == etree.QName(ns['svg'], 'mask') or
gra.tag == etree.QName(ns['svg'], 'filter')):
gra_id = gra.get('id')
gra = etree.tostring(gra, pretty_print=False)
gra = gra.decode('utf-8')
gra_dict[gra_id] = gra
# find all groups
layers = tree.xpath('//svg:g', namespaces=ns) layers = tree.xpath('//svg:g', namespaces=ns)
for layer in layers: for layer in layers:
i = layer.get('id') i = layer.get('id')
if ( # disregard non-content groups if ( # disregard non-content groups
i.endswith("_") or # manually suppressed with underscore i.endswith("_") or # manually suppressed with underscore
i.startswith("XMLID") or # Illustrator generated group i.startswith("XMLID") or # Illustrator generated group
i.startswith("g") # Inkscape generated group i.startswith("g") # Inkscape generated group
): ):
continue continue
# create new canvas # create new canvas
output = copy.deepcopy(template) output = copy.deepcopy(template)
# copy all shapes into template # copy all shapes into template
canvas = output.getroot() canvas = output.getroot()
for e in layer: for e in layer:
canvas.append(e) canvas.append(e)
# represent template as SVG (binary string) # represent template as SVG (binary string)
svg = etree.tostring(output, pretty_print=False) svg = etree.tostring(output, pretty_print=False)
# poor man's conditional defs insertion
# TODO: extract only referenced defs (filters, gradients, ...) # replace <use> with the referenced object
# TODO: detect necessity by traversing the elements. do not stupidly search in the string representation # this is a very poor way to do it. A more elegant way would be to insert
if ("filter:" in svg.decode('utf-8')): # referenced object in the <def> part of the file, but then the colors of
# it seems there is a filter referenced in the generated SVG, re-insert defs from main document # objects with a class definition will not be changed.
canvas.insert(0, defs) # As a substitute the <use> is overwritten with the object.
# re-generate output # THIS WILL REMOVE ANY MODIFICATIONS (movement, clip-paths, etc.)
svg = etree.tostring(output, pretty_print=False) # To prevent this the cloned object should be put into a group, and changes
elif ("clip-path=" in svg.decode('utf-8')): # made to the group.
# it seems there is a clip path referenced in the generated SVG, re-insert defs from main document # TODO: If colors can be changed based on class in defs - put reference in defs instead
canvas.insert(0, defs) if "<svg:use" in svg.decode('utf-8'):
# re-generate output # get a reference map
svg = etree.tostring(output, pretty_print=False) output_map = {c: p for p in canvas.iter() for c in p}
# find use elements
if (output_format == 'tw'): for elem in list(canvas.xpath('//svg:use', namespaces=ns)):
# remove unnecessary attributes # get referenced id
# TODO: never generate unnecessary attributes in the first place def_id = elem.get('{http://www.w3.org/1999/xlink}href')
svg = svg.decode('utf-8') def_id = def_id[1:]
svg = regex_xmlns.sub('', svg) # loop through all layers to find the referenced element
svg = svg.replace(' inkscape:connector-curvature="0"', '') # this just saves space def_orig = None
svg = svg.replace('\n', '').replace('\r', '') # print cannot be multi-line for def_l in layers:
svg = regex_space.sub('><', svg) # remove indentation for e in (e for e in def_l if e.get('id') == def_id):
svg = svg.replace('svg:', '') # svg namespace was removed def_orig = copy.deepcopy(e)
if def_orig is not None:
transformGroups = regex_transformVar.findall(svg) break
parent_index = list(output_map).index(elem)
if (len(transformGroups) > 0): output_map[elem].remove(elem)
svg = regex_transformAttr.sub('', svg) output_map[elem].insert(parent_index, def_orig)
for transformGroup in transformGroups: # Update svg
transformValue = regex_transformValue.search(transformGroup) svg = etree.tostring(output, pretty_print=False)
if (transformValue is not None):
svg = svg.replace(transformGroup, ' transform="\'+' + transformValue.group() + '+\'" ') # internal groups are used for scaling # Conditional defs insertion - only inserts the needed defs
svg_dec = svg.decode('utf-8')
svg = svg.encode('utf-8') if any(x in svg_dec for x in ["filter", "Gradient", "mask=", "clip-path="]):
moreDefsNeeded = True
# save SVG string to file gra_str = '<defs>'
i = layer.get('id') while moreDefsNeeded:
output_path = os.path.join(output_directory, i + '.' + output_format) moreDefsNeeded = False
with open(output_path, 'wb') as f: s = svg.decode('utf-8')
if (output_format == 'svg'): for key in gra_dict.keys():
# Header for normal SVG (XML) if ('#' + key in s or '#' + key in gra_str) and 'id="' + key not in gra_str:
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8")) moreDefsNeeded = True
f.write(svg) gra_str += gra_dict[key]
elif (output_format == 'tw'): gra_str += '</defs>'
# Header for SVG in Twine file (SugarCube print statement) canvas.insert(0, etree.fromstring(gra_str))
f.write((':: Art_Vector_Revamp_%s [nobr]\n\n' % (i)).encode("utf-8")) # re-generate output
f.write("<<print '<html>".encode("utf-8")) svg = etree.tostring(output, pretty_print=False)
f.write(svg)
f.write("</html>' >>".encode("utf-8")) if output_format == 'tw':
# remove unnecessary attributes
# TODO: never generate unnecessary attributes in the first place
svg = svg.decode('utf-8')
svg = regex_xmlns.sub('', svg)
svg = svg.replace('\n', '').replace('\r', '') # print cannot be multi-line
svg = regex_space.sub('><', svg) # remove indentation
svg = svg.replace('svg:', '') # svg namespace was removed
if not svg.endswith(os.linesep):
svg += os.linesep
svg = svg.encode('utf-8')
# save SVG string to file
svg_split_utils.save(layer, args.prefix, output_directory, output_format, svg)